]> git.kaiwu.me - nginx.git/commitdiff
Stream: support ALPN for proxy_ssl upstream.
authorVadim Zhestikov <v.zhestikov@f5.com>
Mon, 2 Feb 2026 22:46:00 +0000 (14:46 -0800)
committerVadimZhestikov <108960056+VadimZhestikov@users.noreply.github.com>
Fri, 17 Apr 2026 21:16:31 +0000 (14:16 -0700)
Added the proxy_ssl_alpn directive, which sets the list of protocols
to advertise via ALPN during upstream TLS handshakes.  Each argument
is a complex value, so variables are accepted.  In particular,

    proxy_ssl_alpn $ssl_alpn_protocol;

inherits the protocol negotiated in the downstream TLS handshake.

When all evaluated values are empty or absent, no ALPN extension is
sent, equivalent to the directive not being set at all.

Closes #406 on GitHub.

src/stream/ngx_stream_proxy_module.c

index 300bdf681878a81620e57e3bcf7a4c562e6274ea..c358c8647f9a2d79a6b16aa3db961d5875bd6113 100644 (file)
@@ -42,6 +42,7 @@ typedef struct {
     ngx_str_t                        ssl_ciphers;
     ngx_stream_complex_value_t      *ssl_name;
     ngx_flag_t                       ssl_server_name;
+    ngx_array_t                     *ssl_alpn;
 
     ngx_flag_t                       ssl_verify;
     ngx_uint_t                       ssl_verify_depth;
@@ -95,6 +96,8 @@ static char *ngx_stream_proxy_bind(ngx_conf_t *cf, ngx_command_t *cmd,
 #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_alpn_set_slot(ngx_conf_t *cf,
+    ngx_command_t *cmd, void *conf);
 static char *ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf,
     ngx_command_t *cmd, void *conf);
 static char *ngx_stream_proxy_ssl_password_file(ngx_conf_t *cf,
@@ -105,6 +108,7 @@ 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 void ngx_stream_proxy_ssl_save_session(ngx_connection_t *c);
 static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s);
+static ngx_int_t ngx_stream_proxy_ssl_alpn(ngx_stream_session_t *s);
 static ngx_int_t ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s);
 static ngx_int_t ngx_stream_proxy_merge_ssl(ngx_conf_t *cf,
     ngx_stream_proxy_srv_conf_t *conf, ngx_stream_proxy_srv_conf_t *prev);
@@ -304,6 +308,13 @@ static ngx_command_t  ngx_stream_proxy_commands[] = {
       offsetof(ngx_stream_proxy_srv_conf_t, ssl_server_name),
       NULL },
 
+    { ngx_string("proxy_ssl_alpn"),
+      NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE,
+      ngx_stream_proxy_ssl_alpn_set_slot,
+      NGX_STREAM_SRV_CONF_OFFSET,
+      0,
+      NULL },
+
     { ngx_string("proxy_ssl_verify"),
       NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG,
       ngx_conf_set_flag_slot,
@@ -1043,6 +1054,61 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s)
 }
 
 
+static char *
+ngx_stream_proxy_ssl_alpn_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf)
+{
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+
+    ngx_stream_proxy_srv_conf_t *pscf = conf;
+
+    ngx_str_t                           *value;
+    ngx_uint_t                           i;
+    ngx_stream_complex_value_t          *cv;
+    ngx_stream_compile_complex_value_t   ccv;
+
+    if (pscf->ssl_alpn != NGX_CONF_UNSET_PTR) {
+        return "is duplicate";
+    }
+
+    value = cf->args->elts;
+
+    pscf->ssl_alpn = ngx_array_create(cf->pool, cf->args->nelts - 1,
+                                      sizeof(ngx_stream_complex_value_t));
+    if (pscf->ssl_alpn == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    cv = ngx_array_push_n(pscf->ssl_alpn, cf->args->nelts - 1);
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));
+
+        ccv.cf = cf;
+        ccv.value = &value[i];
+        ccv.complex_value = &cv[i - 1];
+
+        if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (cv[i - 1].lengths == NULL && value[i].len > 255) {
+            return "protocol too long";
+        }
+    }
+
+    return NGX_CONF_OK;
+
+#else
+    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                       "the \"proxy_ssl_alpn\" directive requires "
+                       "OpenSSL with ALPN support");
+
+    return NGX_CONF_ERROR;
+#endif
+}
+
+
 static char *
 ngx_stream_proxy_ssl_certificate_cache(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf)
@@ -1200,6 +1266,13 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s)
         }
     }
 
+    if (pscf->ssl_alpn) {
+        if (ngx_stream_proxy_ssl_alpn(s) != NGX_OK) {
+            ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
+            return;
+        }
+    }
+
     if (pscf->ssl_certificate
         && pscf->ssl_certificate->value.len
         && (pscf->ssl_certificate->lengths
@@ -1399,6 +1472,82 @@ done:
 }
 
 
+static ngx_int_t
+ngx_stream_proxy_ssl_alpn(ngx_stream_session_t *s)
+{
+#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
+
+    size_t                        len;
+    u_char                       *p, *buf;
+    ngx_str_t                     proto;
+    ngx_uint_t                    i;
+    ngx_connection_t             *c;
+    ngx_stream_upstream_t        *u;
+    ngx_stream_complex_value_t   *cv;
+    ngx_stream_proxy_srv_conf_t  *pscf;
+
+    pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_proxy_module);
+
+    u = s->upstream;
+    c = u->peer.connection;
+
+    len = 0;
+
+    cv = pscf->ssl_alpn->elts;
+
+    for (i = 0; i < pscf->ssl_alpn->nelts; i++) {
+
+        if (ngx_stream_complex_value(s, &cv[i], &proto) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (proto.len == 0 || proto.len > 255) {
+            continue;
+        }
+
+        len += 1 + proto.len;
+    }
+
+    if (len == 0) {
+        return NGX_OK;
+    }
+
+    buf = ngx_pnalloc(c->pool, len);
+    if (buf == NULL) {
+        return NGX_ERROR;
+    }
+
+    p = buf;
+
+    for (i = 0; i < pscf->ssl_alpn->nelts; i++) {
+
+        if (ngx_stream_complex_value(s, &cv[i], &proto) != NGX_OK) {
+            return NGX_ERROR;
+        }
+
+        if (proto.len == 0 || proto.len > 255) {
+            continue;
+        }
+
+        ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
+                       "upstream SSL ALPN: \"%V\"", &proto);
+
+        *p++ = proto.len;
+        p = ngx_cpymem(p, proto.data, proto.len);
+    }
+
+    if (SSL_set_alpn_protos(c->ssl->connection, buf, p - buf) != 0) {
+        ngx_ssl_error(NGX_LOG_ERR, c->log, 0,
+                      "SSL_set_alpn_protos() failed");
+        return NGX_ERROR;
+    }
+
+#endif
+
+    return NGX_OK;
+}
+
+
 static ngx_int_t
 ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s)
 {
@@ -2225,6 +2374,7 @@ ngx_stream_proxy_create_srv_conf(ngx_conf_t *cf)
     conf->ssl_session_reuse = NGX_CONF_UNSET;
     conf->ssl_name = NGX_CONF_UNSET_PTR;
     conf->ssl_server_name = NGX_CONF_UNSET;
+    conf->ssl_alpn = NGX_CONF_UNSET_PTR;
     conf->ssl_verify = NGX_CONF_UNSET;
     conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
     conf->ssl_certificate = NGX_CONF_UNSET_PTR;
@@ -2300,6 +2450,8 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
 
     ngx_conf_merge_value(conf->ssl_server_name, prev->ssl_server_name, 0);
 
+    ngx_conf_merge_ptr_value(conf->ssl_alpn, prev->ssl_alpn, NULL);
+
     ngx_conf_merge_value(conf->ssl_verify, prev->ssl_verify, 0);
 
     ngx_conf_merge_uint_value(conf->ssl_verify_depth,