This is an automated email from the ASF dual-hosted git repository.

AlinsRan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 32cdc62277 feat(stream): match a stream route by several SNIs (#13911)
32cdc62277 is described below

commit 32cdc622777d2b5889671ea971b98b9dc8ed79c5
Author: AlinsRan <[email protected]>
AuthorDate: Wed Sep 16 15:19:12 2026 +0800

    feat(stream): match a stream route by several SNIs (#13911)
---
 apisix/router.lua                 |  10 +
 apisix/schema_def.lua             |  15 +
 apisix/stream/router/ip_port.lua  |  72 +++--
 docs/en/latest/stream-proxy.md    |  22 ++
 docs/zh/latest/stream-proxy.md    |  20 ++
 t/stream-node/stream-route-snis.t | 565 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 679 insertions(+), 25 deletions(-)

diff --git a/apisix/router.lua b/apisix/router.lua
index 6cdd071755..f098f78a89 100644
--- a/apisix/router.lua
+++ b/apisix/router.lua
@@ -43,6 +43,16 @@ local function filter(route)
         end
     end
 
+    -- the same filter runs for stream routes, whose SNIs are matched against
+    -- apisix/ssl.lua's server_name(), which always returns them lowercased
+    if route.value.sni then
+        route.value.sni = str_lower(route.value.sni)
+    elseif route.value.snis then
+        for i, v in ipairs(route.value.snis) do
+            route.value.snis[i] = str_lower(v)
+        end
+    end
+
     apisix_upstream.filter_upstream(route.value.upstream, route)
 end
 
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 85dd409052..959cc941b0 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -1050,6 +1050,16 @@ _M.stream_route = {
             type = "string",
             pattern = host_def_pat,
         },
+        snis = {
+            description = "server name indications, matched as alternatives",
+            type = "array",
+            items = {
+                type = "string",
+                pattern = host_def_pat,
+            },
+            minItems = 1,
+            uniqueItems = true,
+        },
         tls_passthrough = {
             description = "forward the TLS stream to the upstream untouched 
instead of "
                           .. "terminating it here; only consulted on a mixed 
listen, one "
@@ -1063,6 +1073,11 @@ _M.stream_route = {
         plugins = plugins_schema,
         protocol = xrpc_protocol_schema,
     },
+    -- `snis` is the plural form of `sni`, not an addition to it. Carrying both
+    -- would leave the precedence between them to guesswork.
+    ["not"] = {
+        required = {"sni", "snis"},
+    },
     additionalProperties = false,
 }
 
diff --git a/apisix/stream/router/ip_port.lua b/apisix/stream/router/ip_port.lua
index ce0a819108..ecc1b344df 100644
--- a/apisix/stream/router/ip_port.lua
+++ b/apisix/stream/router/ip_port.lua
@@ -61,6 +61,26 @@ local function match_addrs(route, vars)
 end
 
 
+-- Returns the SNIs a stream route is matched by, or nil when it puts no
+-- restriction on the SNI. `sni` and `snis` are the singular and plural form of
+-- the same thing; the schema forbids carrying both.
+local function get_snis(route)
+    local snis = route.snis
+    if not snis then
+        if not route.sni then
+            return nil
+        end
+        snis = {route.sni}
+    end
+
+    if #snis == 0 then
+        return nil
+    end
+
+    return snis
+end
+
+
 local create_router
 do
     local sni_to_items = {}
@@ -91,38 +111,40 @@ do
             if item.value.server_addr then
                 item.value.server_addr_matcher = 
core_ip.create_ip_matcher({item.value.server_addr})
             end
-            if not route.sni then
+            local snis = get_snis(route)
+            if not snis then
                 other_routes[other_routes_idx] = item
                 other_routes_idx = other_routes_idx + 1
                 goto CONTINUE
             end
 
-            local sni_rev = route.sni:reverse()
-            local stored = sni_to_items[sni_rev]
-            if stored then
-                core.table.insert(stored, item)
-                goto CONTINUE
-            end
-
-            sni_to_items[sni_rev] = {item}
-            tls_routes[tls_routes_idx] = {
-                paths = sni_rev,
-                filter_fun = function (vars, opts, ctx)
-                    local items = sni_to_items[sni_rev]
-                    for _, route in ipairs(items) do
-                        local hit = match_addrs(route, vars)
-                        if hit then
-                            ctx.matched_route = route
-                            return true
+            for _, sni in ipairs(snis) do
+                local sni_rev = sni:reverse()
+                local stored = sni_to_items[sni_rev]
+                if stored then
+                    core.table.insert(stored, item)
+                else
+                    sni_to_items[sni_rev] = {item}
+                    tls_routes[tls_routes_idx] = {
+                        paths = sni_rev,
+                        filter_fun = function (vars, opts, ctx)
+                            local items = sni_to_items[sni_rev]
+                            for _, route in ipairs(items) do
+                                local hit = match_addrs(route, vars)
+                                if hit then
+                                    ctx.matched_route = route
+                                    return true
+                                end
+                            end
+                            return false
+                        end,
+                        handler = function (ctx, sni_rev)
+                            -- done in the filter_fun
                         end
-                    end
-                    return false
-                end,
-                handler = function (ctx, sni_rev)
-                    -- done in the filter_fun
+                    }
+                    tls_routes_idx = tls_routes_idx + 1
                 end
-            }
-            tls_routes_idx = tls_routes_idx + 1
+            end
 
             ::CONTINUE::
         end
diff --git a/docs/en/latest/stream-proxy.md b/docs/en/latest/stream-proxy.md
index e7152058de..7b1a2c9175 100644
--- a/docs/en/latest/stream-proxy.md
+++ b/docs/en/latest/stream-proxy.md
@@ -221,6 +221,28 @@ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H 
"X-API-KEY: $admin_ke
 
 In this case, a connection handshaked with SNI `a.test.com` will be proxied to 
`127.0.0.1:5991`.
 
+To serve several names from one route, use `snis`, the plural form of `sni`:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: 
$admin_key" -X PUT -d '
+{
+    "snis": ["a.test.com", "b.test.com"],
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:5991": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
+
+Here a connection handshaked with SNI `a.test.com` or `b.test.com` will be 
proxied to
+`127.0.0.1:5991`. Wildcards are matched as a suffix, so `*.test.com` also 
matches
+`a.b.test.com`, and a single `*` matches any SNI — a connection carrying none 
is still left to
+the routes that match on address instead.
+
+A route can carry `sni` or `snis`, not both.
+
 ## Proxy to TLS over TCP upstream
 
 APISIX also supports proxying to TLS over TCP upstream.
diff --git a/docs/zh/latest/stream-proxy.md b/docs/zh/latest/stream-proxy.md
index bf1cb3ec87..f49723fd0a 100644
--- a/docs/zh/latest/stream-proxy.md
+++ b/docs/zh/latest/stream-proxy.md
@@ -212,6 +212,26 @@ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H 
"X-API-KEY: $admin_ke
 
 在这里,握手时发送 SNI `a.test.com` 的连接会被代理到 `127.0.0.1:5991`。
 
+如果一条路由需要匹配多个名字,使用 `sni` 的复数形式 `snis`:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: 
$admin_key" -X PUT -d '
+{
+    "snis": ["a.test.com", "b.test.com"],
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:5991": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
+
+握手时发送 SNI `a.test.com` 或 `b.test.com` 的连接都会被代理到 `127.0.0.1:5991`。通配符按后缀匹配,
+因此 `*.test.com` 同样匹配 `a.b.test.com`;单独的 `*` 匹配任意 SNI,而不携带 SNI 
的连接仍然交给按地址匹配的路由处理。
+
+一条路由只能携带 `sni` 或 `snis` 其中之一。
+
 ## 代理到基于 TCP 的 TLS 上游
 
 APISIX 还支持代理到基于 TCP 的 TLS 上游。
diff --git a/t/stream-node/stream-route-snis.t 
b/t/stream-node/stream-route-snis.t
new file mode 100644
index 0000000000..f1d74dd191
--- /dev/null
+++ b/t/stream-node/stream-route-snis.t
@@ -0,0 +1,565 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+log_level('info');
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    # The TLS requests are sent from inside the same nginx instance that runs
+    # the setup, so that the SNI router is exercised only once the stream
+    # subsystem has synced the configuration from etcd.
+    # An empty response body therefore means that no stream route was matched:
+    # apisix closes the connection without proxying anything.
+    $block->set_value("stream_conf_enable", 1);
+
+    my $config = $block->config // '';
+    $config .= <<_EOC_;
+    location /tls {
+        content_by_lua_block {
+            local sock = ngx.socket.tcp()
+            sock:settimeout(2000)
+            local ok, err = sock:connect("127.0.0.1", 2005)
+            if not ok then
+                ngx.say("failed to connect: ", err)
+                return
+            end
+
+            local sess, err = sock:sslhandshake(nil, ngx.var.arg_sni, false)
+            if not sess then
+                sock:close()
+                ngx.say("failed to do SSL handshake: ", err)
+                return
+            end
+
+            local bytes, err = sock:send("mmm")
+            if not bytes then
+                sock:close()
+                ngx.say("send stream request error: ", err)
+                return
+            end
+
+            -- reaching peer closure yields `nil, "closed", partial`; the bytes
+            -- read so far are the response
+            local data, err, partial = sock:receive("*a")
+            sock:close()
+            if not data and err ~= "closed" then
+                ngx.say("receive stream response error: ", err)
+                return
+            end
+
+            ngx.print(data or partial or "")
+        }
+    }
+_EOC_
+
+    $block->set_value("config", $config);
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: every sni of the route is matched
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["a.test.com", "b.test.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=a.test.com", "GET /tls?sni=b.test.com", "GET 
/tls?sni=c.test.com"]
+--- response_body eval
+["passed\n", "hello world\n", "hello world\n", ""]
+
+
+
+=== TEST 2: the singular sni still works on its own
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "sni": "a.test.com",
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=a.test.com", "GET /tls?sni=b.test.com"]
+--- response_body eval
+["passed\n", "hello world\n", ""]
+
+
+
+=== TEST 3: sni and snis can not be carried together
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+
+            local code, body = t.test('/apisix/admin/stream_routes/1', 
ngx.HTTP_PUT,
+                [[{
+                    "sni": "a.test.com",
+                    "snis": ["b.test.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            ngx.say(code)
+        }
+    }
+--- request
+GET /t
+--- response_body
+400
+
+
+
+=== TEST 4: a wildcard sni is a suffix match
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["*.test.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=a.test.com", "GET /tls?sni=a.b.test.com", "GET 
/tls?sni=test.org"]
+--- response_body eval
+["passed\n", "hello world\n", "hello world\n", ""]
+
+
+
+=== TEST 5: a bare * matches any SNI, but not a connection carrying none
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["*"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=whatever.test.com", "GET 
/tls?sni=another.test.org"]
+--- response_body eval
+["passed\n", "hello world\n", "hello world\n"]
+
+
+
+=== TEST 6: the snis are matched case-insensitively
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["Mixed.TEST.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=mixed.test.com", "GET /tls?sni=other.test.com"]
+--- response_body eval
+["passed\n", "hello world\n", ""]
+
+
+
+=== TEST 7: the snis are matched on a TLS passthrough listen
+--- stream_server_config
+    listen 2005;
+    ssl_preread on;
+
+    preread_by_lua_block {
+        ngx.sleep(0.1)
+        apisix.stream_preread_phase(true)
+    }
+
+    proxy_pass apisix_backend;
+--- extra_stream_config
+    server {
+        listen 1997 ssl;
+        ssl_certificate     cert/apisix.crt;
+        ssl_certificate_key cert/apisix.key;
+        content_by_lua_block {
+            ngx.say("hello from the backend")
+        }
+    }
+--- config
+    location /setup {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+
+            -- the listen carries no `ssl`, so it holds no certificate and a
+            -- completed handshake can only have been terminated by the backend
+            local code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["a.test.com", "b.test.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1997": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=a.test.com", "GET /tls?sni=b.test.com", "GET 
/tls?sni=c.test.com"]
+--- response_body_like eval
+[qr/^passed$/, qr/^hello from the backend$/, qr/^hello from the backend$/,
+ qr/failed to do SSL handshake/]
+--- no_error_log
+[alert]
+
+
+
+=== TEST 8: the singular sni is matched case-insensitively too
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "sni": "Mixed.SNI.com",
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=mixed.sni.com", "GET /tls?sni=other.sni.com"]
+--- response_body eval
+["passed\n", "hello world\n", ""]
+
+
+
+=== TEST 9: two routes sharing one sni are told apart by their address
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            -- both carry a.test.com, so they share one radixtree entry and are
+            -- separated by match_addrs inside its filter_fun
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["a.test.com"],
+                    "server_port": 1985,
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route 1: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/2', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["a.test.com"],
+                    "server_port": 2005,
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route 2: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=a.test.com"]
+--- response_body eval
+["passed\n", "hello world\n"]
+
+
+
+=== TEST 10: shrinking the sni list stops matching the removed name
+--- config
+    location /setup {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local code = t.test('/apisix/admin/ssls/1', ngx.HTTP_PUT,
+                core.json.encode({cert = ssl_cert, key = ssl_key, sni = "*"}))
+            if code >= 300 then
+                ngx.say("failed to create ssl: ", code)
+                return
+            end
+
+            code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["a.test.com", "b.test.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+
+    location /shrink {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+
+            local code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["a.test.com"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1995": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to update stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("shrunk")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=b.test.com", "GET /shrink", "GET 
/tls?sni=b.test.com", "GET /tls?sni=a.test.com"]
+--- response_body eval
+["passed\n", "hello world\n", "shrunk\n", "", "hello world\n"]
+
+
+
+=== TEST 11: a bare * on a passthrough listen still needs an SNI
+--- stream_server_config
+    listen 2005;
+    ssl_preread on;
+
+    preread_by_lua_block {
+        ngx.sleep(0.1)
+        apisix.stream_preread_phase(true)
+    }
+
+    proxy_pass apisix_backend;
+--- extra_stream_config
+    server {
+        listen 1997 ssl;
+        ssl_certificate     cert/apisix.crt;
+        ssl_certificate_key cert/apisix.key;
+        content_by_lua_block {
+            ngx.say("hello from the backend")
+        }
+    }
+--- config
+    location /setup {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+
+            local code = t.test('/apisix/admin/stream_routes/1', ngx.HTTP_PUT,
+                [[{
+                    "snis": ["*"],
+                    "upstream": {
+                        "nodes": {"127.0.0.1:1997": 1},
+                        "type": "roundrobin"
+                    }
+                }]])
+            if code >= 300 then
+                ngx.say("failed to create stream route: ", code)
+                return
+            end
+
+            ngx.sleep(0.5)
+            ngx.say("passed")
+        }
+    }
+--- pipelined_requests eval
+["GET /setup", "GET /tls?sni=anything.test.com", "GET /tls"]
+--- response_body_like eval
+[qr/^passed$/, qr/^hello from the backend$/, qr/failed to do SSL handshake/]
+--- no_error_log
+[alert]

Reply via email to