From ab1bec0174e643e88cc6081d27839e082a01f1fb Mon Sep 17 00:00:00 2001 From: Vadim Zhestikov Date: Thu, 16 Apr 2026 14:25:58 -0700 Subject: [PATCH] Stream: add PROXY protocol v2 ssl raw TLV body support. Add an "ssl" named alias to proxy_protocol_tlv that passes the PP2_TYPE_SSL (0x20) TLV body verbatim to the upstream, rather than having nginx assemble it from individual ssl sub-TLV directives. This is useful for forwarding an incoming SSL TLV unchanged, e.g. via a stream variable that carries the raw bytes. Coexistence rules enforced at config time: - "ssl" cannot appear more than once. - "ssl" cannot coexist with ssl_verify, ssl_version, ssl_cn, ssl_cipher, ssl_sig_alg, ssl_key_alg, or ssl_0x entries, since those build the same 0x20 TLV through assembly. --- src/stream/ngx_stream_proxy_module.c | 57 +++++++++++++++++++++------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 0ea04d463..60a4e3431 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -23,6 +23,7 @@ typedef struct { ngx_uint_t type; ngx_uint_t is_ssl_sub; ngx_uint_t is_ssl_verify; + ngx_uint_t is_ssl_raw; ngx_stream_complex_value_t *value; } ngx_stream_proxy_protocol_tlv_t; @@ -3119,21 +3120,23 @@ typedef struct { ngx_uint_t type; ngx_uint_t is_ssl_sub; ngx_uint_t is_ssl_verify; + ngx_uint_t is_ssl_raw; } ngx_stream_proxy_tlv_name_t; static ngx_stream_proxy_tlv_name_t ngx_stream_proxy_tlv_names[] = { - { ngx_string("alpn"), 0x01, 0, 0 }, - { ngx_string("authority"), 0x02, 0, 0 }, - { ngx_string("unique_id"), 0x05, 0, 0 }, - { ngx_string("ssl_verify"), 0x00, 0, 1 }, - { ngx_string("ssl_version"), 0x21, 1, 0 }, - { ngx_string("ssl_cn"), 0x22, 1, 0 }, - { ngx_string("ssl_cipher"), 0x23, 1, 0 }, - { ngx_string("ssl_sig_alg"), 0x24, 1, 0 }, - { ngx_string("ssl_key_alg"), 0x25, 1, 0 }, - { ngx_string("netns"), 0x30, 0, 0 }, - { ngx_null_string, 0x00, 0, 0 } + { ngx_string("alpn"), 0x01, 0, 0, 0 }, + { ngx_string("authority"), 0x02, 0, 0, 0 }, + { ngx_string("unique_id"), 0x05, 0, 0, 0 }, + { ngx_string("ssl"), 0x20, 0, 0, 1 }, + { ngx_string("ssl_verify"), 0x00, 0, 1, 0 }, + { ngx_string("ssl_version"), 0x21, 1, 0, 0 }, + { ngx_string("ssl_cn"), 0x22, 1, 0, 0 }, + { ngx_string("ssl_cipher"), 0x23, 1, 0, 0 }, + { ngx_string("ssl_sig_alg"), 0x24, 1, 0, 0 }, + { ngx_string("ssl_key_alg"), 0x25, 1, 0, 0 }, + { ngx_string("netns"), 0x30, 0, 0, 0 }, + { ngx_null_string, 0x00, 0, 0, 0 } }; @@ -3144,7 +3147,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_uint_t j; ngx_int_t type; - ngx_uint_t is_ssl_sub, is_ssl_verify; + ngx_uint_t is_ssl_sub, is_ssl_verify, is_ssl_raw; ngx_str_t *value; ngx_stream_compile_complex_value_t ccv; ngx_stream_proxy_protocol_tlv_t *tlv, *existing; @@ -3155,6 +3158,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) type = -1; is_ssl_sub = 0; is_ssl_verify = 0; + is_ssl_raw = 0; /* check named aliases first */ for (nm = ngx_stream_proxy_tlv_names; nm->name.len; nm++) { @@ -3164,6 +3168,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) type = (ngx_int_t) nm->type; is_ssl_sub = nm->is_ssl_sub; is_ssl_verify = nm->is_ssl_verify; + is_ssl_raw = nm->is_ssl_raw; break; } } @@ -3224,9 +3229,33 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } } else { - /* duplicate detection */ + /* duplicate and coexistence detection */ existing = pscf->proxy_protocol_tlvs->elts; for (j = 0; j < pscf->proxy_protocol_tlvs->nelts; j++) { + if (is_ssl_raw) { + /* "ssl" raw body cannot coexist with sub-TLV directives */ + if (existing[j].is_ssl_raw) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate PROXY protocol TLV " + "\"ssl\""); + return NGX_CONF_ERROR; + } + if (existing[j].is_ssl_sub || existing[j].is_ssl_verify) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"ssl\" TLV conflicts with ssl " + "sub-TLV directives"); + return NGX_CONF_ERROR; + } + continue; + } + if (existing[j].is_ssl_raw + && (is_ssl_sub || is_ssl_verify)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "ssl sub-TLV directives conflict with " + "\"ssl\" TLV"); + return NGX_CONF_ERROR; + } if (is_ssl_verify) { if (existing[j].is_ssl_verify) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -3235,6 +3264,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } } else if (!existing[j].is_ssl_verify + && !existing[j].is_ssl_raw && existing[j].is_ssl_sub == is_ssl_sub && existing[j].type == (ngx_uint_t) type) { @@ -3254,6 +3284,7 @@ ngx_stream_proxy_protocol_tlv(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) tlv->type = (ngx_uint_t) type; tlv->is_ssl_sub = is_ssl_sub; tlv->is_ssl_verify = is_ssl_verify; + tlv->is_ssl_raw = is_ssl_raw; tlv->value = ngx_palloc(cf->pool, sizeof(ngx_stream_complex_value_t)); if (tlv->value == NULL) {