From d76e3d301644cfc6a2d914976b6098eb98b9e5b9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 20 May 2025 15:33:20 +0400 Subject: [PATCH] HTTP CONNECT proxy. HTTP CONNECT method is now supported in HTTP/1 connections. It's disabled in all currently existing standard modules. A new variable $port is added that contains the port passed by client in HTTP CONNECT. The $host variable contains the host part. A new module ngx_http_tunnel module is added which establishes a tunnel to a backend. It supports the newly added HTTP CONNECT method and can be used to set up an HTTP CONNECT proxy. As recommended by RFC 9110, proxy target should be restricted to ensure safe proxying: : Proxies that support CONNECT SHOULD restrict its use to a limited set : of known ports or a configurable list of safe request targets. Example config: server { listen 8000; resolver dns.example.com; map $port $tun_port { 80 1; 443 1; } map $host $tun_host { hostnames; example.com 1; *.example.org 1; } map $tun_port$tun_host $tun { 11 $host:$port; } location / { tunnel_pass $tun; } } Request: $ curl -px 127.0.0.1:8000 http://example.com --- auto/modules | 11 + auto/options | 2 + .../modules/ngx_http_chunked_filter_module.c | 7 + src/http/modules/ngx_http_fastcgi_module.c | 4 + src/http/modules/ngx_http_grpc_module.c | 4 + src/http/modules/ngx_http_scgi_module.c | 4 + src/http/modules/ngx_http_tunnel_module.c | 513 ++++++++++++++++++ src/http/modules/ngx_http_uwsgi_module.c | 4 + src/http/ngx_http_core_module.c | 1 + src/http/ngx_http_parse.c | 121 ++++- src/http/ngx_http_request.c | 33 +- src/http/ngx_http_request.h | 3 + src/http/ngx_http_upstream.c | 12 + src/http/ngx_http_upstream.h | 1 + src/http/ngx_http_variables.c | 17 + 15 files changed, 729 insertions(+), 8 deletions(-) create mode 100644 src/http/modules/ngx_http_tunnel_module.c diff --git a/auto/modules b/auto/modules index 38b3aba78..a473656e5 100644 --- a/auto/modules +++ b/auto/modules @@ -781,6 +781,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_TUNNEL = YES ]; then + ngx_module_name=ngx_http_tunnel_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/modules/ngx_http_tunnel_module.c + ngx_module_libs= + ngx_module_link=$HTTP_TUNNEL + + . auto/module + fi + if [ $HTTP_PERL != NO ]; then ngx_module_name=ngx_http_perl_module ngx_module_incs=src/http/modules/perl diff --git a/auto/options b/auto/options index 6a6e990a0..8c09433f9 100644 --- a/auto/options +++ b/auto/options @@ -89,6 +89,7 @@ HTTP_FASTCGI=YES HTTP_UWSGI=YES HTTP_SCGI=YES HTTP_GRPC=YES +HTTP_TUNNEL=YES HTTP_PERL=NO HTTP_MEMCACHED=YES HTTP_LIMIT_CONN=YES @@ -279,6 +280,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" --without-http_uwsgi_module) HTTP_UWSGI=NO ;; --without-http_scgi_module) HTTP_SCGI=NO ;; --without-http_grpc_module) HTTP_GRPC=NO ;; + --without-http_tunnel_module) HTTP_TUNNEL=NO ;; --without-http_memcached_module) HTTP_MEMCACHED=NO ;; --without-http_limit_conn_module) HTTP_LIMIT_CONN=NO ;; --without-http_limit_req_module) HTTP_LIMIT_REQ=NO ;; diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c index 4d6fd3eed..ecb7b8d91 100644 --- a/src/http/modules/ngx_http_chunked_filter_module.c +++ b/src/http/modules/ngx_http_chunked_filter_module.c @@ -71,6 +71,13 @@ ngx_http_chunked_header_filter(ngx_http_request_t *r) return ngx_http_next_header_filter(r); } + if (r->method == NGX_HTTP_CONNECT + && r->headers_out.status >= NGX_HTTP_OK + && r->headers_out.status < NGX_HTTP_SPECIAL_RESPONSE) + { + return ngx_http_next_header_filter(r); + } + if (r->headers_out.content_length_n == -1 || r->expect_trailers) { diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index a41b496dc..2f402f37a 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -688,6 +688,10 @@ ngx_http_fastcgi_handler(ngx_http_request_t *r) ngx_http_fastcgi_main_conf_t *fmcf; #endif + if (r->method == NGX_HTTP_CONNECT) { + return NGX_HTTP_NOT_ALLOWED; + } + if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 80046d6a4..147240a7c 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -552,6 +552,10 @@ ngx_http_grpc_handler(ngx_http_request_t *r) ngx_http_grpc_ctx_t *ctx; ngx_http_grpc_loc_conf_t *glcf; + if (r->method == NGX_HTTP_CONNECT) { + return NGX_HTTP_NOT_ALLOWED; + } + if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index 9023a36e4..516b393fb 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -485,6 +485,10 @@ ngx_http_scgi_handler(ngx_http_request_t *r) ngx_http_scgi_main_conf_t *smcf; #endif + if (r->method == NGX_HTTP_CONNECT) { + return NGX_HTTP_NOT_ALLOWED; + } + if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/modules/ngx_http_tunnel_module.c b/src/http/modules/ngx_http_tunnel_module.c new file mode 100644 index 000000000..3f603f071 --- /dev/null +++ b/src/http/modules/ngx_http_tunnel_module.c @@ -0,0 +1,513 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +typedef struct { + ngx_http_upstream_conf_t upstream; + + ngx_array_t *tunnel_lengths; + ngx_array_t *tunnel_values; +} ngx_http_tunnel_loc_conf_t; + + +static ngx_int_t ngx_http_tunnel_eval(ngx_http_request_t *r, + ngx_http_tunnel_loc_conf_t *tlcf); +static ngx_int_t ngx_http_tunnel_create_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_tunnel_reinit_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_tunnel_process_header(ngx_http_request_t *r); +static void ngx_http_tunnel_abort_request(ngx_http_request_t *r); +static void ngx_http_tunnel_finalize_request(ngx_http_request_t *r, + ngx_int_t rc); + +static void *ngx_http_tunnel_create_loc_conf(ngx_conf_t *cf); +static char *ngx_http_tunnel_merge_loc_conf(ngx_conf_t *cf, + void *parent, void *child); + +static char *ngx_http_tunnel_pass(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + +static char *ngx_http_tunnel_lowat_check(ngx_conf_t *cf, void *post, void *data); + + +static ngx_conf_post_t ngx_http_tunnel_lowat_post = + { ngx_http_tunnel_lowat_check }; + + +static ngx_conf_bitmask_t ngx_http_tunnel_next_upstream_masks[] = { + { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR }, + { ngx_string("timeout"), NGX_HTTP_UPSTREAM_FT_TIMEOUT }, + { ngx_string("off"), NGX_HTTP_UPSTREAM_FT_OFF }, + { ngx_null_string, 0 } +}; + + +ngx_module_t ngx_http_tunnel_module; + + +static ngx_command_t ngx_http_tunnel_commands[] = { + + { ngx_string("tunnel_pass"), + NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, + ngx_http_tunnel_pass, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("tunnel_bind"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12, + ngx_http_upstream_bind_set_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.local), + NULL }, + + { ngx_string("tunnel_socket_keepalive"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.socket_keepalive), + NULL }, + + { ngx_string("tunnel_connect_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.connect_timeout), + NULL }, + + { ngx_string("tunnel_send_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.send_timeout), + NULL }, + + { ngx_string("tunnel_send_lowat"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.send_lowat), + &ngx_http_tunnel_lowat_post }, + + { ngx_string("tunnel_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.buffer_size), + NULL }, + + { ngx_string("tunnel_read_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.read_timeout), + NULL }, + + { ngx_string("tunnel_next_upstream"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_conf_set_bitmask_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.next_upstream), + &ngx_http_tunnel_next_upstream_masks }, + + { ngx_string("tunnel_next_upstream_tries"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.next_upstream_tries), + NULL }, + + { ngx_string("tunnel_next_upstream_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_tunnel_loc_conf_t, upstream.next_upstream_timeout), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_tunnel_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_tunnel_create_loc_conf, /* create location configuration */ + ngx_http_tunnel_merge_loc_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_tunnel_module = { + NGX_MODULE_V1, + &ngx_http_tunnel_module_ctx, /* module context */ + ngx_http_tunnel_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_int_t +ngx_http_tunnel_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_upstream_t *u; + ngx_http_tunnel_loc_conf_t *tlcf; + + if (r->method != NGX_HTTP_CONNECT) { + return NGX_HTTP_NOT_ALLOWED; + } + + if (ngx_http_upstream_create(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + tlcf = ngx_http_get_module_loc_conf(r, ngx_http_tunnel_module); + + if (tlcf->tunnel_lengths) { + if (ngx_http_tunnel_eval(r, tlcf) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + u = r->upstream; + + u->conf = &tlcf->upstream; + + u->create_request = ngx_http_tunnel_create_request; + u->reinit_request = ngx_http_tunnel_reinit_request; + u->process_header = ngx_http_tunnel_process_header; + u->abort_request = ngx_http_tunnel_abort_request; + u->finalize_request = ngx_http_tunnel_finalize_request; + r->state = 0; + + rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + return rc; + } + + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_tunnel_eval(ngx_http_request_t *r, ngx_http_tunnel_loc_conf_t *tlcf) +{ + ngx_url_t url; + ngx_http_upstream_t *u; + + ngx_memzero(&url, sizeof(ngx_url_t)); + + if (ngx_http_script_run(r, &url.url, tlcf->tunnel_lengths->elts, 0, + tlcf->tunnel_values->elts) + == NULL) + { + return NGX_ERROR; + } + + url.no_resolve = 1; + + if (ngx_parse_url(r->pool, &url) != NGX_OK) { + if (url.err) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "%s in upstream \"%V\"", url.err, &url.url); + } + + return NGX_ERROR; + } + + u = r->upstream; + + u->resolved = ngx_pcalloc(r->pool, sizeof(ngx_http_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; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_tunnel_create_request(ngx_http_request_t *r) +{ + /* u->request_bufs = NULL */ + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_tunnel_reinit_request(ngx_http_request_t *r) +{ + r->state = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_tunnel_process_header(ngx_http_request_t *r) +{ + ngx_http_upstream_t *u; + + u = r->upstream; + + u->headers_in.status_n = NGX_HTTP_OK; + ngx_str_set(&u->headers_in.status_line, "200 OK"); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http tunnel status %ui \"%V\"", + u->headers_in.status_n, &u->headers_in.status_line); + + u->keepalive = 0; + u->upgrade = 1; + + return NGX_OK; +} + + +static void +ngx_http_tunnel_abort_request(ngx_http_request_t *r) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort http tunnel request"); + + return; +} + + +static void +ngx_http_tunnel_finalize_request(ngx_http_request_t *r, ngx_int_t rc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize http tunnel request"); + + return; +} + + +static void * +ngx_http_tunnel_create_loc_conf(ngx_conf_t *cf) +{ + ngx_http_tunnel_loc_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_tunnel_loc_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->upstream.next_upstream = 0; + */ + + conf->upstream.next_upstream_tries = NGX_CONF_UNSET_UINT; + + conf->upstream.local = NGX_CONF_UNSET_PTR; + conf->upstream.socket_keepalive = NGX_CONF_UNSET; + + conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC; + conf->upstream.send_timeout = NGX_CONF_UNSET_MSEC; + conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC; + conf->upstream.next_upstream_timeout = NGX_CONF_UNSET_MSEC; + + conf->upstream.send_lowat = NGX_CONF_UNSET_SIZE; + conf->upstream.buffer_size = NGX_CONF_UNSET_SIZE; + + conf->upstream.ignore_input = 1; + + ngx_str_set(&conf->upstream.module, "tunnel"); + + return conf; +} + + +static char * +ngx_http_tunnel_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_tunnel_loc_conf_t *prev = parent; + ngx_http_tunnel_loc_conf_t *conf = child; + + ngx_http_core_loc_conf_t *clcf; + + ngx_conf_merge_uint_value(conf->upstream.next_upstream_tries, + prev->upstream.next_upstream_tries, 0); + + ngx_conf_merge_ptr_value(conf->upstream.local, + prev->upstream.local, NULL); + + ngx_conf_merge_value(conf->upstream.socket_keepalive, + prev->upstream.socket_keepalive, 0); + + ngx_conf_merge_msec_value(conf->upstream.connect_timeout, + prev->upstream.connect_timeout, 60000); + + ngx_conf_merge_msec_value(conf->upstream.send_timeout, + prev->upstream.send_timeout, 60000); + + ngx_conf_merge_msec_value(conf->upstream.read_timeout, + prev->upstream.read_timeout, 60000); + + ngx_conf_merge_msec_value(conf->upstream.next_upstream_timeout, + prev->upstream.next_upstream_timeout, 0); + + ngx_conf_merge_size_value(conf->upstream.send_lowat, + prev->upstream.send_lowat, 0); + + ngx_conf_merge_size_value(conf->upstream.buffer_size, + prev->upstream.buffer_size, + (size_t) ngx_pagesize); + + ngx_conf_merge_bitmask_value(conf->upstream.next_upstream, + prev->upstream.next_upstream, + (NGX_CONF_BITMASK_SET + |NGX_HTTP_UPSTREAM_FT_ERROR + |NGX_HTTP_UPSTREAM_FT_TIMEOUT)); + + if (conf->upstream.next_upstream & NGX_HTTP_UPSTREAM_FT_OFF) { + conf->upstream.next_upstream = NGX_CONF_BITMASK_SET + |NGX_HTTP_UPSTREAM_FT_OFF; + } + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + + if (clcf->noname + && conf->upstream.upstream == NULL && conf->tunnel_lengths == NULL) + { + conf->upstream.upstream = prev->upstream.upstream; + + conf->tunnel_lengths = prev->tunnel_lengths; + conf->tunnel_values = prev->tunnel_values; + } + + if (clcf->lmt_excpt && clcf->handler == NULL + && (conf->upstream.upstream || conf->tunnel_lengths)) + { + clcf->handler = ngx_http_tunnel_handler; + } + + + return NGX_CONF_OK; +} + + +static char * +ngx_http_tunnel_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_tunnel_loc_conf_t *tlcf = conf; + + ngx_url_t u; + ngx_str_t *value, *url; + ngx_uint_t n; + ngx_http_core_loc_conf_t *clcf; + ngx_http_script_compile_t sc; + + if (tlcf->upstream.upstream || tlcf->tunnel_lengths) { + return "is duplicate"; + } + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + + clcf->handler = ngx_http_tunnel_handler; + + if (clcf->name.len && clcf->name.data[clcf->name.len - 1] == '/') { + clcf->auto_redirect = 1; + } + + value = cf->args->elts; + + url = &value[1]; + + n = ngx_http_script_variables_count(url); + + if (n) { + + ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); + + sc.cf = cf; + sc.source = url; + sc.lengths = &tlcf->tunnel_lengths; + sc.values = &tlcf->tunnel_values; + sc.variables = n; + sc.complete_lengths = 1; + sc.complete_values = 1; + + if (ngx_http_script_compile(&sc) != NGX_OK) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *url; + u.no_resolve = 1; + + tlcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0); + if (tlcf->upstream.upstream == NULL) { + return NGX_CONF_ERROR; + } + + return NGX_CONF_OK; +} + + +static char * +ngx_http_tunnel_lowat_check(ngx_conf_t *cf, void *post, void *data) +{ +#if (NGX_FREEBSD) + ssize_t *np = data; + + if ((u_long) *np >= ngx_freebsd_net_inet_tcp_sendspace) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"tunnel_send_lowat\" must be less than %d " + "(sysctl net.inet.tcp.sendspace)", + ngx_freebsd_net_inet_tcp_sendspace); + + return NGX_CONF_ERROR; + } + +#elif !(NGX_HAVE_SO_SNDLOWAT) + ssize_t *np = data; + + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "\"tunnel_send_lowat\" is not supported, ignored"); + + *np = 0; + +#endif + + return NGX_CONF_OK; +} diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index 51a861d9a..47afca203 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -661,6 +661,10 @@ ngx_http_uwsgi_handler(ngx_http_request_t *r) ngx_http_uwsgi_main_conf_t *uwmcf; #endif + if (r->method == NGX_HTTP_CONNECT) { + return NGX_HTTP_NOT_ALLOWED; + } + if (ngx_http_upstream_create(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 92c3eae8a..103949a08 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -4615,6 +4615,7 @@ static ngx_http_method_name_t ngx_methods_names[] = { { (u_char *) "LOCK", (uint32_t) ~NGX_HTTP_LOCK }, { (u_char *) "UNLOCK", (uint32_t) ~NGX_HTTP_UNLOCK }, { (u_char *) "PATCH", (uint32_t) ~NGX_HTTP_PATCH }, + { (u_char *) "CONNECT", (uint32_t) ~NGX_HTTP_CONNECT }, { NULL, 0 } }; diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index a45c04554..7d684969b 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -116,6 +116,13 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) sw_host_end, sw_host_ip_literal, sw_port, + sw_spaces_before_connect_host, + sw_connect_host_start, + sw_connect_host, + sw_connect_host_end, + sw_connect_port_start, + sw_connect_port, + sw_connect_host_ip_literal, sw_after_slash_in_uri, sw_check_uri, sw_uri, @@ -158,6 +165,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) if (ch == ' ') { r->method_end = p - 1; m = r->request_start; + state = sw_spaces_before_uri; switch (p - m) { @@ -247,6 +255,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) if (ngx_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', ' ')) { r->method = NGX_HTTP_CONNECT; + state = sw_spaces_before_connect_host; } break; @@ -269,7 +278,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) break; } - state = sw_spaces_before_uri; break; } @@ -630,6 +638,117 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) } break; + case sw_spaces_before_connect_host: + + if (ch == ' ') { + break; + } + + /* fall through */ + + case sw_connect_host_start: + + r->host_start = p; + + if (ch == '[') { + state = sw_connect_host_ip_literal; + break; + } + + state = sw_connect_host; + + /* fall through */ + + case sw_connect_host: + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + break; + } + + if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') { + break; + } + + /* fall through */ + + case sw_connect_host_end: + + switch (ch) { + case ':': + r->host_end = p; + state = sw_connect_port_start; + break; + default: + return NGX_HTTP_PARSE_INVALID_REQUEST; + } + break; + + case sw_connect_port_start: + + r->port_start = p; + state = sw_connect_port; + + /* fall through */ + + case sw_connect_port: + + if (ch >= '0' && ch <= '9') { + break; + } + + r->port_end = p; + + switch (ch) { + case ' ': + state = sw_http_09; + break; + default: + return NGX_HTTP_PARSE_INVALID_REQUEST; + } + break; + + case sw_connect_host_ip_literal: + + if (ch >= '0' && ch <= '9') { + break; + } + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + break; + } + + switch (ch) { + case ':': + break; + case ']': + state = sw_connect_host_end; + break; + case '-': + case '.': + case '_': + case '~': + /* unreserved */ + break; + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + /* sub-delims */ + break; + default: + return NGX_HTTP_PARSE_INVALID_REQUEST; + } + break; + /* space+ after URI */ case sw_http_09: switch (ch) { diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index ceac8d307..8c1e9915d 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -1192,6 +1192,20 @@ ngx_http_process_request_line(ngx_event_t *rev) r->headers_in.server = host; } + if (r->port_end) { + rc = ngx_atoi(r->port_start, r->port_end - r->port_start); + + if (rc < 1 || rc > 65535) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid port in request line"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + r->headers_in.port.len = r->port_end - r->port_start; + r->headers_in.port.data = r->port_start; + } + if (r->http_version < NGX_HTTP_VERSION_10) { if (r->headers_in.server.len == 0 @@ -1271,6 +1285,11 @@ ngx_http_process_request_uri(ngx_http_request_t *r) { ngx_http_core_srv_conf_t *cscf; + if (r->uri_end == NULL) { + r->uri_start = (u_char *) "/"; + r->uri_end = r->uri_start + 1; + } + if (r->args_start) { r->uri.len = r->args_start - 1 - r->uri_start; } else { @@ -1768,6 +1787,13 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, } } + if (r->port_end) { + r->port_start = new + (r->port_start - old); + if (r->port_end) { + r->port_end = new + (r->port_end - old); + } + } + if (r->uri_ext) { r->uri_ext = new + (r->uri_ext - old); } @@ -2055,13 +2081,6 @@ ngx_http_process_request_header(ngx_http_request_t *r) } } - if (r->method == NGX_HTTP_CONNECT) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, - "client sent CONNECT method"); - ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); - return NGX_ERROR; - } - if (r->method == NGX_HTTP_TRACE) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent TRACE method"); diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 9407f46ae..c5c8f939f 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -238,6 +238,7 @@ typedef struct { ngx_str_t passwd; ngx_str_t server; + ngx_str_t port; off_t content_length_n; time_t keep_alive_n; @@ -600,6 +601,8 @@ struct ngx_http_request_s { u_char *schema_end; u_char *host_start; u_char *host_end; + u_char *port_start; + u_char *port_end; unsigned http_minor:16; unsigned http_major:16; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index d4cf1b7fe..0acd97ed8 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2221,6 +2221,11 @@ ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u, return; } + if (u->conf->ignore_input) { + ngx_http_upstream_process_header(r, u); + return; + } + ngx_add_timer(c->read, u->conf->read_timeout); if (c->read->ready) { @@ -2476,6 +2481,11 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) #endif } + if (u->conf->ignore_input) { + rc = u->process_header(r); + goto done; + } + for ( ;; ) { n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); @@ -2533,6 +2543,8 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) break; } +done: + if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); return; diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index e0a903669..ce15cac37 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -234,6 +234,7 @@ typedef struct { unsigned intercept_404:1; unsigned change_buffering:1; unsigned preserve_output:1; + unsigned ignore_input:1; #if (NGX_HTTP_SSL || NGX_COMPAT) ngx_ssl_t *ssl; diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c index 4f0bd0e4b..8f0946b0d 100644 --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c @@ -51,6 +51,8 @@ static ngx_int_t ngx_http_variable_content_length(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_port(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, @@ -192,6 +194,7 @@ static ngx_http_variable_t ngx_http_core_variables[] = { offsetof(ngx_http_request_t, headers_in.content_type), 0, 0 }, { ngx_string("host"), NULL, ngx_http_variable_host, 0, 0, 0 }, + { ngx_string("port"), NULL, ngx_http_variable_port, 0, 0, 0 }, { ngx_string("binary_remote_addr"), NULL, ngx_http_variable_binary_remote_addr, 0, 0, 0 }, @@ -1244,6 +1247,20 @@ ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v, } +static ngx_int_t +ngx_http_variable_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, + uintptr_t data) +{ + v->len = r->headers_in.port.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = r->headers_in.port.data; + + return NGX_OK; +} + + static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)