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
This commit is contained in:
Roman Arutyunyan 2025-05-20 15:33:20 +04:00
parent 6a134dfd48
commit d76e3d3016
15 changed files with 729 additions and 8 deletions

View File

@ -781,6 +781,17 @@ if [ $HTTP = YES ]; then
. auto/module . auto/module
fi 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 if [ $HTTP_PERL != NO ]; then
ngx_module_name=ngx_http_perl_module ngx_module_name=ngx_http_perl_module
ngx_module_incs=src/http/modules/perl ngx_module_incs=src/http/modules/perl

View File

@ -89,6 +89,7 @@ HTTP_FASTCGI=YES
HTTP_UWSGI=YES HTTP_UWSGI=YES
HTTP_SCGI=YES HTTP_SCGI=YES
HTTP_GRPC=YES HTTP_GRPC=YES
HTTP_TUNNEL=YES
HTTP_PERL=NO HTTP_PERL=NO
HTTP_MEMCACHED=YES HTTP_MEMCACHED=YES
HTTP_LIMIT_CONN=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_uwsgi_module) HTTP_UWSGI=NO ;;
--without-http_scgi_module) HTTP_SCGI=NO ;; --without-http_scgi_module) HTTP_SCGI=NO ;;
--without-http_grpc_module) HTTP_GRPC=NO ;; --without-http_grpc_module) HTTP_GRPC=NO ;;
--without-http_tunnel_module) HTTP_TUNNEL=NO ;;
--without-http_memcached_module) HTTP_MEMCACHED=NO ;; --without-http_memcached_module) HTTP_MEMCACHED=NO ;;
--without-http_limit_conn_module) HTTP_LIMIT_CONN=NO ;; --without-http_limit_conn_module) HTTP_LIMIT_CONN=NO ;;
--without-http_limit_req_module) HTTP_LIMIT_REQ=NO ;; --without-http_limit_req_module) HTTP_LIMIT_REQ=NO ;;

View File

@ -71,6 +71,13 @@ ngx_http_chunked_header_filter(ngx_http_request_t *r)
return ngx_http_next_header_filter(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 if (r->headers_out.content_length_n == -1
|| r->expect_trailers) || r->expect_trailers)
{ {

View File

@ -688,6 +688,10 @@ ngx_http_fastcgi_handler(ngx_http_request_t *r)
ngx_http_fastcgi_main_conf_t *fmcf; ngx_http_fastcgi_main_conf_t *fmcf;
#endif #endif
if (r->method == NGX_HTTP_CONNECT) {
return NGX_HTTP_NOT_ALLOWED;
}
if (ngx_http_upstream_create(r) != NGX_OK) { if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }

View File

@ -552,6 +552,10 @@ ngx_http_grpc_handler(ngx_http_request_t *r)
ngx_http_grpc_ctx_t *ctx; ngx_http_grpc_ctx_t *ctx;
ngx_http_grpc_loc_conf_t *glcf; 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) { if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }

View File

@ -485,6 +485,10 @@ ngx_http_scgi_handler(ngx_http_request_t *r)
ngx_http_scgi_main_conf_t *smcf; ngx_http_scgi_main_conf_t *smcf;
#endif #endif
if (r->method == NGX_HTTP_CONNECT) {
return NGX_HTTP_NOT_ALLOWED;
}
if (ngx_http_upstream_create(r) != NGX_OK) { if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }

View File

@ -0,0 +1,513 @@
/*
* Copyright (C) Roman Arutyunyan
* Copyright (C) Nginx, Inc.
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
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;
}

View File

@ -661,6 +661,10 @@ ngx_http_uwsgi_handler(ngx_http_request_t *r)
ngx_http_uwsgi_main_conf_t *uwmcf; ngx_http_uwsgi_main_conf_t *uwmcf;
#endif #endif
if (r->method == NGX_HTTP_CONNECT) {
return NGX_HTTP_NOT_ALLOWED;
}
if (ngx_http_upstream_create(r) != NGX_OK) { if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR; return NGX_HTTP_INTERNAL_SERVER_ERROR;
} }

View File

@ -4615,6 +4615,7 @@ static ngx_http_method_name_t ngx_methods_names[] = {
{ (u_char *) "LOCK", (uint32_t) ~NGX_HTTP_LOCK }, { (u_char *) "LOCK", (uint32_t) ~NGX_HTTP_LOCK },
{ (u_char *) "UNLOCK", (uint32_t) ~NGX_HTTP_UNLOCK }, { (u_char *) "UNLOCK", (uint32_t) ~NGX_HTTP_UNLOCK },
{ (u_char *) "PATCH", (uint32_t) ~NGX_HTTP_PATCH }, { (u_char *) "PATCH", (uint32_t) ~NGX_HTTP_PATCH },
{ (u_char *) "CONNECT", (uint32_t) ~NGX_HTTP_CONNECT },
{ NULL, 0 } { NULL, 0 }
}; };

View File

@ -116,6 +116,13 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
sw_host_end, sw_host_end,
sw_host_ip_literal, sw_host_ip_literal,
sw_port, 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_after_slash_in_uri,
sw_check_uri, sw_check_uri,
sw_uri, sw_uri,
@ -158,6 +165,7 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
if (ch == ' ') { if (ch == ' ') {
r->method_end = p - 1; r->method_end = p - 1;
m = r->request_start; m = r->request_start;
state = sw_spaces_before_uri;
switch (p - m) { 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', ' ')) if (ngx_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', ' '))
{ {
r->method = NGX_HTTP_CONNECT; r->method = NGX_HTTP_CONNECT;
state = sw_spaces_before_connect_host;
} }
break; break;
@ -269,7 +278,6 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
break; break;
} }
state = sw_spaces_before_uri;
break; break;
} }
@ -630,6 +638,117 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
} }
break; 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 */ /* space+ after URI */
case sw_http_09: case sw_http_09:
switch (ch) { switch (ch) {

View File

@ -1192,6 +1192,20 @@ ngx_http_process_request_line(ngx_event_t *rev)
r->headers_in.server = host; 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->http_version < NGX_HTTP_VERSION_10) {
if (r->headers_in.server.len == 0 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; 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) { if (r->args_start) {
r->uri.len = r->args_start - 1 - r->uri_start; r->uri.len = r->args_start - 1 - r->uri_start;
} else { } 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) { if (r->uri_ext) {
r->uri_ext = new + (r->uri_ext - old); 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) { if (r->method == NGX_HTTP_TRACE) {
ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, ngx_log_error(NGX_LOG_INFO, r->connection->log, 0,
"client sent TRACE method"); "client sent TRACE method");

View File

@ -238,6 +238,7 @@ typedef struct {
ngx_str_t passwd; ngx_str_t passwd;
ngx_str_t server; ngx_str_t server;
ngx_str_t port;
off_t content_length_n; off_t content_length_n;
time_t keep_alive_n; time_t keep_alive_n;
@ -600,6 +601,8 @@ struct ngx_http_request_s {
u_char *schema_end; u_char *schema_end;
u_char *host_start; u_char *host_start;
u_char *host_end; u_char *host_end;
u_char *port_start;
u_char *port_end;
unsigned http_minor:16; unsigned http_minor:16;
unsigned http_major:16; unsigned http_major:16;

View File

@ -2221,6 +2221,11 @@ ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u,
return; return;
} }
if (u->conf->ignore_input) {
ngx_http_upstream_process_header(r, u);
return;
}
ngx_add_timer(c->read, u->conf->read_timeout); ngx_add_timer(c->read, u->conf->read_timeout);
if (c->read->ready) { if (c->read->ready) {
@ -2476,6 +2481,11 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
#endif #endif
} }
if (u->conf->ignore_input) {
rc = u->process_header(r);
goto done;
}
for ( ;; ) { for ( ;; ) {
n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last); 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; break;
} }
done:
if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) { if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER); ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
return; return;

View File

@ -234,6 +234,7 @@ typedef struct {
unsigned intercept_404:1; unsigned intercept_404:1;
unsigned change_buffering:1; unsigned change_buffering:1;
unsigned preserve_output:1; unsigned preserve_output:1;
unsigned ignore_input:1;
#if (NGX_HTTP_SSL || NGX_COMPAT) #if (NGX_HTTP_SSL || NGX_COMPAT)
ngx_ssl_t *ssl; ngx_ssl_t *ssl;

View File

@ -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); ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r, static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data); 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, static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data); ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r, 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 }, offsetof(ngx_http_request_t, headers_in.content_type), 0, 0 },
{ ngx_string("host"), NULL, ngx_http_variable_host, 0, 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_string("binary_remote_addr"), NULL,
ngx_http_variable_binary_remote_addr, 0, 0, 0 }, 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 static ngx_int_t
ngx_http_variable_binary_remote_addr(ngx_http_request_t *r, ngx_http_variable_binary_remote_addr(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data) ngx_http_variable_value_t *v, uintptr_t data)