diff --git a/.github/workflows/autodocs.yml b/.github/workflows/autodocs.yml index 12dcea67243..baf03c474da 100644 --- a/.github/workflows/autodocs.yml +++ b/.github/workflows/autodocs.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v4 - name: Lookup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-deps with: path: ${{ env.INSTALL_ROOT }} @@ -94,7 +94,7 @@ jobs: ref: ${{ github.event.inputs.target_branch }} - name: Lookup build cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-deps with: path: ${{ env.INSTALL_ROOT }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 88704ccdedc..b815a183274 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: - name: Lookup build cache id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ steps.cache-key.outputs.cache-key }} diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 8cb47a16550..7b8170b387e 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -59,7 +59,7 @@ jobs: - name: Lookup build cache id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ needs.build.outputs.cache-key }} @@ -108,7 +108,7 @@ jobs: - name: Download runtimes file uses: Kong/gh-storage/download@v1 env: - GITHUB_TOKEN: ${{ secrets.PAT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: repo-path: Kong/gateway-action-storage/main/.ci/runtimes.json @@ -187,7 +187,7 @@ jobs: - name: Lookup build cache id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ needs.build.outputs.cache-key }} @@ -345,7 +345,7 @@ jobs: - name: Lookup build cache id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ needs.build.outputs.cache-key }} diff --git a/.github/workflows/perf.yml b/.github/workflows/perf.yml index d71b8851903..7bc69ee2bfe 100644 --- a/.github/workflows/perf.yml +++ b/.github/workflows/perf.yml @@ -42,7 +42,7 @@ jobs: - name: Lookup build cache id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ steps.cache-key.outputs.cache-key }} @@ -118,7 +118,7 @@ jobs: - name: Load Cached Packages id: cache-deps if: env.GHA_CACHE == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ needs.build-packages.outputs.cache-key }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a40ff4d3ae..bc07e202999 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -132,7 +132,7 @@ jobs: - name: Cache Git id: cache-git if: (matrix.package == 'rpm' || matrix.image == 'debian:10') && matrix.image != '' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /usr/local/git key: ${{ matrix.label }}-git-2.41.0 @@ -193,7 +193,7 @@ jobs: - name: Cache Packages id: cache-deps if: env.GHA_CACHE == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: bazel-bin/pkg key: ${{ steps.cache-key.outputs.cache-key }} @@ -401,7 +401,6 @@ jobs: - name: Comment on commit if: github.event_name == 'push' && matrix.label == 'ubuntu' uses: peter-evans/commit-comment@5a6f8285b8f2e8376e41fe1b563db48e6cf78c09 # v3.0.0 - continue-on-error: true # TODO: temporary fix until the token is back with: token: ${{ secrets.GHA_COMMENT_TOKEN }} body: | diff --git a/.github/workflows/upgrade-tests.yml b/.github/workflows/upgrade-tests.yml index 96effbccc5f..d3c75d916a6 100644 --- a/.github/workflows/upgrade-tests.yml +++ b/.github/workflows/upgrade-tests.yml @@ -47,7 +47,7 @@ jobs: - name: Lookup build cache id: cache-deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.BUILD_ROOT }} key: ${{ needs.build.outputs.cache-key }} diff --git a/.requirements b/.requirements index 286634dc112..d002fb23a4b 100644 --- a/.requirements +++ b/.requirements @@ -1,7 +1,7 @@ KONG_PACKAGE_NAME=kong OPENRESTY=1.25.3.1 -LUAROCKS=3.9.2 +LUAROCKS=3.10.0 OPENSSL=3.2.1 PCRE=10.43 LIBEXPAT=2.5.0 diff --git a/build/luarocks/luarocks_repositories.bzl b/build/luarocks/luarocks_repositories.bzl index 588595faf3d..7741e138b45 100644 --- a/build/luarocks/luarocks_repositories.bzl +++ b/build/luarocks/luarocks_repositories.bzl @@ -10,7 +10,7 @@ def luarocks_repositories(): name = "luarocks", build_file = "//build/luarocks:BUILD.luarocks.bazel", strip_prefix = "luarocks-" + version, - sha256 = "bca6e4ecc02c203e070acdb5f586045d45c078896f6236eb46aa33ccd9b94edb", + sha256 = "e9bf06d5ec6b8ecc6dbd1530d2d77bdb3377d814a197c46388e9f148548c1c89", urls = [ "https://luarocks.org/releases/luarocks-" + version + ".tar.gz", ], diff --git a/build/luarocks/luarocks_wrap_script.lua b/build/luarocks/luarocks_wrap_script.lua index 44e03cbaceb..18999e11a22 100644 --- a/build/luarocks/luarocks_wrap_script.lua +++ b/build/luarocks/luarocks_wrap_script.lua @@ -20,8 +20,8 @@ if install_dest:sub(-1) ~= "/" then install_dest = install_dest .. "/" end -- HACK -cfg.lua_interpreter = "luajit" cfg.sysconfdir = install_dest .. "etc/luarocks" +cfg.variables["LUA"] = install_dest .. "openresty/luajit/bin/luajit" cfg.variables["LUA_DIR"] = install_dest .. "openresty/luajit" cfg.variables["LUA_INCDIR"] = install_dest .. "openresty/luajit/include/luajit-2.1" cfg.variables["LUA_BINDIR"] = install_dest .. "openresty/luajit/bin" diff --git a/build/openresty/patches/ngx_lua-0.10.26_03-regex-memory-corruption.patch b/build/openresty/patches/ngx_lua-0.10.26_03-regex-memory-corruption.patch new file mode 100644 index 00000000000..1c40fd5fa57 --- /dev/null +++ b/build/openresty/patches/ngx_lua-0.10.26_03-regex-memory-corruption.patch @@ -0,0 +1,38 @@ +diff --git a/bundle/ngx_lua-0.10.26/src/ngx_http_lua_regex.c b/bundle/ngx_lua-0.10.26/src/ngx_http_lua_regex.c +index 1b52fa2..30c1650 100644 +--- a/bundle/ngx_lua-0.10.26/src/ngx_http_lua_regex.c ++++ b/bundle/ngx_lua-0.10.26/src/ngx_http_lua_regex.c +@@ -688,11 +688,11 @@ ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + ngx_pool_t *old_pool; + + if (flags & NGX_LUA_RE_MODE_DFA) { +- ovecsize = 2; ++ ovecsize = 1; + re->ncaptures = 0; + + } else { +- ovecsize = (re->ncaptures + 1) * 3; ++ ovecsize = re->ncaptures + 1; + } + + old_pool = ngx_http_lua_pcre_malloc_init(NULL); +@@ -710,7 +710,7 @@ ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + } + + ngx_regex_match_data_size = ovecsize; +- ngx_regex_match_data = pcre2_match_data_create(ovecsize / 3, NULL); ++ ngx_regex_match_data = pcre2_match_data_create(ovecsize, NULL); + + if (ngx_regex_match_data == NULL) { + rc = PCRE2_ERROR_NOMEMORY; +@@ -756,8 +756,8 @@ ngx_http_lua_ffi_exec_regex(ngx_http_lua_regex_t *re, int flags, + "n %ui, ovecsize %ui", flags, exec_opts, rc, n, ovecsize); + #endif + +- if (!(flags & NGX_LUA_RE_MODE_DFA) && n > ovecsize / 3) { +- n = ovecsize / 3; ++ if (n > ovecsize) { ++ n = ovecsize; + } + + for (i = 0; i < n; i++) { diff --git a/build/openresty/patches/ngx_stream_lua-0.0.14_02-remove-useless-pcre-config.patch b/build/openresty/patches/ngx_stream_lua-0.0.14_02-remove-useless-pcre-config.patch new file mode 100644 index 00000000000..1e706fc6c3e --- /dev/null +++ b/build/openresty/patches/ngx_stream_lua-0.0.14_02-remove-useless-pcre-config.patch @@ -0,0 +1,59 @@ +From f1499e3b06f698dc2813e0686aa0cc257299fcd7 Mon Sep 17 00:00:00 2001 +From: swananan +Date: Thu, 11 Jan 2024 08:46:17 +0800 +Subject: [PATCH] changes: remove the useless pcre config. + +--- + config | 39 --------------------------------------- + 1 file changed, 39 deletions(-) + +diff --git a/bundle/ngx_stream_lua-0.0.14/config b/bundle/ngx_stream_lua-0.0.14/config +index 8db90628..e1470b7a 100644 +--- a/bundle/ngx_stream_lua-0.0.14/config ++++ b/bundle/ngx_stream_lua-0.0.14/config +@@ -405,45 +405,6 @@ fi + + # ---------------------------------------- + +-if [ $USE_PCRE = YES -o $PCRE != NONE ] && [ $PCRE != NO -a $PCRE != YES ] && [ $PCRE2 != YES ]; then +- # force pcre_version symbol to be required when PCRE is statically linked +- case "$NGX_PLATFORM" in +- Darwin:*) +- ngx_feature="require defined symbols (-u)" +- ngx_feature_name= +- ngx_feature_path= +- ngx_feature_libs="-Wl,-u,_strerror" +- ngx_feature_run=no +- ngx_feature_incs="#include " +- ngx_feature_test='printf("hello");' +- +- . auto/feature +- +- if [ $ngx_found = yes ]; then +- CORE_LIBS="-Wl,-u,_pcre_version $CORE_LIBS" +- fi +- ;; +- +- *) +- ngx_feature="require defined symbols (--require-defined)" +- ngx_feature_name= +- ngx_feature_path= +- ngx_feature_libs="-Wl,--require-defined=strerror" +- ngx_feature_run=no +- ngx_feature_incs="#include " +- ngx_feature_test='printf("hello");' +- +- . auto/feature +- +- if [ $ngx_found = yes ]; then +- CORE_LIBS="-Wl,--require-defined=pcre_version $CORE_LIBS" +- fi +- ;; +- esac +-fi +- +-# ---------------------------------------- +- + USE_MD5=YES + USE_SHA1=YES + diff --git a/build/openresty/patches/ngx_stream_lua-0.0.14_03-regex-memory-corruption.patch b/build/openresty/patches/ngx_stream_lua-0.0.14_03-regex-memory-corruption.patch new file mode 100644 index 00000000000..197a0e054b8 --- /dev/null +++ b/build/openresty/patches/ngx_stream_lua-0.0.14_03-regex-memory-corruption.patch @@ -0,0 +1,60 @@ +diff --git a/bundle/ngx_stream_lua-0.0.14/src/ngx_stream_lua_regex.c b/bundle/ngx_stream_lua-0.0.14/src/ngx_stream_lua_regex.c +index e32744e..241ec00 100644 +--- a/bundle/ngx_stream_lua-0.0.14/src/ngx_stream_lua_regex.c ++++ b/bundle/ngx_stream_lua-0.0.14/src/ngx_stream_lua_regex.c +@@ -695,11 +695,11 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, + ngx_pool_t *old_pool; + + if (flags & NGX_LUA_RE_MODE_DFA) { +- ovecsize = 2; ++ ovecsize = 1; + re->ncaptures = 0; + + } else { +- ovecsize = (re->ncaptures + 1) * 3; ++ ovecsize = re->ncaptures + 1; + } + + old_pool = ngx_stream_lua_pcre_malloc_init(NULL); +@@ -717,7 +717,7 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, + } + + ngx_regex_match_data_size = ovecsize; +- ngx_regex_match_data = pcre2_match_data_create(ovecsize / 3, NULL); ++ ngx_regex_match_data = pcre2_match_data_create(ovecsize, NULL); + + if (ngx_regex_match_data == NULL) { + rc = PCRE2_ERROR_NOMEMORY; +@@ -762,8 +762,8 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, + "n %ui, ovecsize %ui", flags, exec_opts, rc, n, ovecsize); + #endif + +- if (!(flags & NGX_LUA_RE_MODE_DFA) && n > ovecsize / 3) { +- n = ovecsize / 3; ++ if (n > ovecsize) { ++ n = ovecsize; + } + + for (i = 0; i < n; i++) { +@@ -796,6 +796,21 @@ ngx_stream_lua_ffi_exec_regex(ngx_stream_lua_regex_t *re, int flags, + re->ncaptures = 0; + + } else { ++ /* How pcre_exec() returns captured substrings ++ * The first two-thirds of the vector is used to pass back captured ++ * substrings, each substring using a pair of integers. The remaining ++ * third of the vector is used as workspace by pcre_exec() while ++ * matching capturing subpatterns, and is not available for passing ++ * back information. The number passed in ovecsize should always be a ++ * multiple of three. If it is not, it is rounded down. ++ * ++ * When a match is successful, information about captured substrings is ++ * returned in pairs of integers, starting at the beginning of ovector, ++ * and continuing up to two-thirds of its length at the most. The first ++ * element of each pair is set to the byte offset of the first character ++ * in a substring, and the second is set to the byte offset of the first ++ * character after the end of a substring. ++ */ + ovecsize = (re->ncaptures + 1) * 3; + } + diff --git a/build/templates/venv-commons b/build/templates/venv-commons index f13613ca71d..f16a5aadbde 100644 --- a/build/templates/venv-commons +++ b/build/templates/venv-commons @@ -42,7 +42,7 @@ $KONG_VENV/openresty/site/lualib/?.lua;$KONG_VENV/openresty/site/lualib/?.ljbc;\ $KONG_VENV/openresty/site/lualib/?/init.lua;$KONG_VENV/openresty/site/lualib/?/init.ljbc;\ $KONG_VENV/openresty/lualib/?.lua;$KONG_VENV/openresty/lualib/?.ljbc;\ $KONG_VENV/openresty/lualib/?/init.lua;$KONG_VENV/openresty/lualib/?/init.ljbc;\ -$KONG_VENV/openresty/luajit/share/luajit-2.1.0-beta3/?.lua" +$KONG_VENV/openresty/luajit/share/luajit-2.1/?.lua" # support custom plugin development if [ -n $KONG_PLUGIN_PATH ] ; then diff --git a/changelog/unreleased/kong/bump-luarocks.yml b/changelog/unreleased/kong/bump-luarocks.yml new file mode 100644 index 00000000000..843bfaf358d --- /dev/null +++ b/changelog/unreleased/kong/bump-luarocks.yml @@ -0,0 +1,2 @@ +message: "Bumped LuaRocks from 3.9.2 to 3.10.0" +type: dependency diff --git a/changelog/unreleased/kong/separate_kong_cache_invalidation_cluster_event_channel.yml b/changelog/unreleased/kong/separate_kong_cache_invalidation_cluster_event_channel.yml new file mode 100644 index 00000000000..ab0c68bc357 --- /dev/null +++ b/changelog/unreleased/kong/separate_kong_cache_invalidation_cluster_event_channel.yml @@ -0,0 +1,4 @@ +message: | + Each Kong cache instance now utilizes its own cluster event channel. This approach isolates cache invalidation events and reducing the generation of unnecessary worker events. +type: bugfix +scope: Core diff --git a/kong-3.7.0-0.rockspec b/kong-3.7.0-0.rockspec index 0d37a900f09..3d1bdc2839d 100644 --- a/kong-3.7.0-0.rockspec +++ b/kong-3.7.0-0.rockspec @@ -65,6 +65,7 @@ build = { ["kong.router.expressions"] = "kong/router/expressions.lua", ["kong.router.atc"] = "kong/router/atc.lua", ["kong.router.fields"] = "kong/router/fields.lua", + ["kong.router.transform"] = "kong/router/transform.lua", ["kong.router.utils"] = "kong/router/utils.lua", ["kong.conf_loader"] = "kong/conf_loader/init.lua", @@ -610,6 +611,5 @@ build = { ["kong.timing.hooks.socket"] = "kong/timing/hooks/socket.lua", ["kong.dynamic_hook"] = "kong/dynamic_hook/init.lua", - ["kong.dynamic_hook.wrap_function_gen"] = "kong/dynamic_hook/wrap_function_gen.lua", } } diff --git a/kong/cache/init.lua b/kong/cache/init.lua index dcf2d173c13..91b21c64a1a 100644 --- a/kong/cache/init.lua +++ b/kong/cache/init.lua @@ -86,6 +86,10 @@ function _M.new(opts) error("opts.resty_lock_opts must be a table", 2) end + if opts.invalidation_channel and type(opts.invalidation_channel) ~= "string" then + error("opts.invalidation_channel must be a string", 2) + end + local shm_name = opts.shm_name if not shared[shm_name] then log(ERR, "shared dictionary ", shm_name, " not found") @@ -131,6 +135,8 @@ function _M.new(opts) end local cluster_events = opts.cluster_events + local invalidation_channel = opts.invalidation_channel + or ("invalidations_" .. shm_name) local self = { cluster_events = cluster_events, mlcache = mlcache, @@ -138,10 +144,11 @@ function _M.new(opts) shm_name = shm_name, ttl = ttl, neg_ttl = neg_ttl, + invalidation_channel = invalidation_channel, } - local ok, err = cluster_events:subscribe("invalidations", function(key) - log(DEBUG, "received invalidate event from cluster for key: '", key, "'") + local ok, err = cluster_events:subscribe(self.invalidation_channel, function(key) + log(DEBUG, self.shm_name .. " received invalidate event from cluster for key: '", key, "'") self:invalidate_local(key) end) if not ok then @@ -230,7 +237,7 @@ function _M:invalidate_local(key) error("key must be a string", 2) end - log(DEBUG, "invalidating (local): '", key, "'") + log(DEBUG, self.shm_name, " invalidating (local): '", key, "'") local ok, err = self.mlcache:delete(key) if not ok then @@ -248,7 +255,7 @@ function _M:invalidate(key) log(DEBUG, "broadcasting (cluster) invalidation for key: '", key, "'") - local ok, err = self.cluster_events:broadcast("invalidations", key) + local ok, err = self.cluster_events:broadcast(self.invalidation_channel, key) if not ok then log(ERR, "failed to broadcast cached entity invalidation: ", err) end diff --git a/kong/dynamic_hook/init.lua b/kong/dynamic_hook/init.lua index f86f09311b3..d5cd940b0f1 100644 --- a/kong/dynamic_hook/init.lua +++ b/kong/dynamic_hook/init.lua @@ -1,19 +1,25 @@ -local warp_function_gen = require("kong.dynamic_hook.wrap_function_gen") - -local ngx = ngx - -local _M = { +local ngx = ngx +local type = type +local pcall = pcall +local select = select +local ipairs = ipairs +local assert = assert +local ngx_log = ngx.log +local ngx_WARN = ngx.WARN +local ngx_get_phase = ngx.get_phase + + +local _M = { TYPE = { BEFORE = 1, - AFTER = 2, + AFTER = 2, BEFORE_MUT = 3, - AFTER_MUT = 4, + AFTER_MUT = 4, }, } -local pcall = pcall -local non_function_hooks = { +local NON_FUNCTION_HOOKS = { --[[ [group_name] = { [hook_name] = , @@ -23,39 +29,152 @@ local non_function_hooks = { --]] } -local always_enabled_groups = {} - -local wrap_functions = { - [0] = warp_function_gen.generate_wrap_function(0), - [1] = warp_function_gen.generate_wrap_function(1), - [2] = warp_function_gen.generate_wrap_function(2), - [3] = warp_function_gen.generate_wrap_function(3), - [4] = warp_function_gen.generate_wrap_function(4), - [5] = warp_function_gen.generate_wrap_function(5), - [6] = warp_function_gen.generate_wrap_function(6), - [7] = warp_function_gen.generate_wrap_function(7), - [8] = warp_function_gen.generate_wrap_function(8), - ["varargs"] = warp_function_gen.generate_wrap_function("varargs"), -} + +local ALWAYS_ENABLED_GROUPS = {} + + +local function should_execute_original_func(group_name) + if ALWAYS_ENABLED_GROUPS[group_name] then + return + end + + local phase = ngx_get_phase() + if phase == "init" or phase == "init_worker" then + return true + end + + local dynamic_hook = ngx.ctx.dynamic_hook + if not dynamic_hook then + return true + end + + local enabled_groups = dynamic_hook.enabled_groups + if not enabled_groups[group_name] then + return true + end +end + + +local function execute_hook_vararg(hook, hook_type, group_name, ...) + if not hook then + return + end + local ok, err = pcall(hook, ...) + if not ok then + ngx_log(ngx_WARN, "failed to run ", hook_type, " hook of ", group_name, ": ", err) + end +end + + +local function execute_hooks_vararg(hooks, hook_type, group_name, ...) + if not hooks then + return + end + for _, hook in ipairs(hooks) do + execute_hook_vararg(hook, hook_type, group_name, ...) + end +end + + +local function execute_after_hooks_vararg(handlers, group_name, ...) + execute_hook_vararg(handlers.after_mut, "after_mut", group_name, ...) + execute_hooks_vararg(handlers.afters, "after", group_name, ...) + return ... +end + + +local function wrap_function_vararg(group_name, original_func, handlers) + return function (...) + if should_execute_original_func(group_name) then + return original_func(...) + end + execute_hooks_vararg(handlers.befores, "before", group_name, ...) + return execute_after_hooks_vararg(handlers, group_name, original_func(...)) + end +end + + +local function execute_hook(hook, hook_type, group_name, a1, a2, a3, a4, a5, a6, a7, a8) + if not hook then + return + end + local ok, err = pcall(hook, a1, a2, a3, a4, a5, a6, a7, a8) + if not ok then + ngx_log(ngx_WARN, "failed to run ", hook_type, " hook of ", group_name, ": ", err) + end +end + + +local function execute_hooks(hooks, hook_type, group_name, a1, a2, a3, a4, a5, a6, a7, a8) + if not hooks then + return + end + for _, hook in ipairs(hooks) do + execute_hook(hook, hook_type, group_name, a1, a2, a3, a4, a5, a6, a7, a8) + end +end + + +local function execute_original_func(max_args, original_func, a1, a2, a3, a4, a5, a6, a7, a8) + if max_args == 0 then + return original_func() + elseif max_args == 1 then + return original_func(a1) + elseif max_args == 2 then + return original_func(a1, a2) + elseif max_args == 3 then + return original_func(a1, a2, a3) + elseif max_args == 4 then + return original_func(a1, a2, a3, a4) + elseif max_args == 5 then + return original_func(a1, a2, a3, a4, a5) + elseif max_args == 6 then + return original_func(a1, a2, a3, a4, a5, a6) + elseif max_args == 7 then + return original_func(a1, a2, a3, a4, a5, a6, a7) + else + return original_func(a1, a2, a3, a4, a5, a6, a7, a8) + end +end + + +local function wrap_function(max_args, group_name, original_func, handlers) + return function(a1, a2, a3, a4, a5, a6, a7, a8) + if should_execute_original_func(group_name) then + a1, a2, a3, a4, a5, a6, a7, a8 = execute_original_func(max_args, original_func, a1, a2, a3, a4, a5, a6, a7, a8) + + else + execute_hook(handlers.before_mut, "before_mut", group_name, a1, a2, a3, a4, a5, a6, a7, a8) + execute_hooks(handlers.befores, "before", group_name, a1, a2, a3, a4, a5, a6, a7, a8) + a1, a2, a3, a4, a5, a6, a7, a8 = execute_original_func(max_args, original_func, a1, a2, a3, a4, a5, a6, a7, a8) + execute_hook(handlers.after_mut, "after_mut", group_name, a1, a2, a3, a4, a5, a6, a7, a8) + execute_hooks(handlers.afters, "after", group_name, a1, a2, a3, a4, a5, a6, a7, a8) + end + return a1, a2, a3, a4, a5, a6, a7, a8 + end +end function _M.hook_function(group_name, parent, child_key, max_args, handlers) assert(type(parent) == "table", "parent must be a table") assert(type(child_key) == "string", "child_key must be a string") - if type(max_args) == "string" then - assert(max_args == "varargs", "max_args must be a number or \"varargs\"") + local is_varargs = max_args == "varargs" + if is_varargs then assert(handlers.before_mut == nil, "before_mut is not supported for varargs functions") - else - assert(type(max_args) == "number", "max_args must be a number or \"varargs\"") - assert(max_args >= 0 and max_args <= 8, "max_args must be >= 0") + assert(type(max_args) == "number", 'max_args must be a number or "varargs"') + assert(max_args >= 0 and max_args <= 8, 'max_args must be >= 0 and <= 8, or "varargs"') end - local old_func = parent[child_key] - assert(type(old_func) == "function", "parent[" .. child_key .. "] must be a function") + local original_func = parent[child_key] + assert(type(original_func) == "function", "parent[" .. child_key .. "] must be a function") - parent[child_key] = wrap_functions[max_args](always_enabled_groups, group_name, old_func, handlers) + if is_varargs then + parent[child_key] = wrap_function_vararg(group_name, original_func, handlers) + else + parent[child_key] = wrap_function(max_args, group_name, original_func, handlers) + end end @@ -64,10 +183,10 @@ function _M.hook(group_name, hook_name, handler) assert(type(hook_name) == "string", "hook_name must be a string") assert(type(handler) == "function", "handler must be a function") - local hooks = non_function_hooks[group_name] + local hooks = NON_FUNCTION_HOOKS[group_name] if not hooks then hooks = {} - non_function_hooks[group_name] = hooks + NON_FUNCTION_HOOKS[group_name] = hooks end hooks[hook_name] = handler @@ -75,7 +194,7 @@ end function _M.is_group_enabled(group_name) - if always_enabled_groups[group_name] then + if ALWAYS_ENABLED_GROUPS[group_name] then return true end @@ -93,12 +212,12 @@ function _M.is_group_enabled(group_name) end -function _M.run_hooks(ctx, group_name, hook_name, ...) +function _M.run_hooks(group_name, hook_name, a1, a2, a3, a4, a5, a6, a7, a8, ...) if not _M.is_group_enabled(group_name) then return end - local hooks = non_function_hooks[group_name] + local hooks = NON_FUNCTION_HOOKS[group_name] if not hooks then return end @@ -108,30 +227,35 @@ function _M.run_hooks(ctx, group_name, hook_name, ...) return end - local ok, err = pcall(handler, ...) + local argc = select("#", ...) + local ok, err + if argc == 0 then + ok, err = pcall(handler, a1, a2, a3, a4, a5, a6, a7, a8) + else + ok, err = pcall(handler, a1, a2, a3, a4, a5, a6, a7, a8, ...) + end if not ok then - ngx.log(ngx.WARN, - string.format("failed to run dynamic hook %s.%s: %s", - group_name, hook_name, err)) + ngx_log(ngx_WARN, "failed to run dynamic hook ", group_name, ".", hook_name, ": ", err) end end -function _M.enable_on_this_request(group_name) - local info = ngx.ctx.dynamic_hook - if not info then - info = { - enabled_groups = {}, +function _M.enable_on_this_request(group_name, ngx_ctx) + ngx_ctx = ngx_ctx or ngx.ctx + if ngx_ctx.dynamic_hook then + ngx_ctx.dynamic_hook.enabled_groups[group_name] = true + else + ngx_ctx.dynamic_hook = { + enabled_groups = { + [group_name] = true + }, } - ngx.ctx.dynamic_hook = info end - - info.enabled_groups[group_name] = true end function _M.always_enable(group_name) - always_enabled_groups[group_name] = true + ALWAYS_ENABLED_GROUPS[group_name] = true end diff --git a/kong/dynamic_hook/wrap_function_gen.lua b/kong/dynamic_hook/wrap_function_gen.lua deleted file mode 100644 index dddddb55635..00000000000 --- a/kong/dynamic_hook/wrap_function_gen.lua +++ /dev/null @@ -1,224 +0,0 @@ -local ngx_get_phase = ngx.get_phase - -local TEMPLATE = [[ - return function(always_enabled_groups, group_name, original_func, handlers) - -- we cannot access upvalue here as this function is generated - local ngx = ngx - local ngx_get_phase = ngx.get_phase - - return function(%s) - if not always_enabled_groups[group_name] then - local phase = ngx_get_phase() - if phase == "init" or phase == "init_worker" then - return original_func(%s) - end - local dynamic_hook = ngx.ctx.dynamic_hook - if not dynamic_hook then - return original_func(%s) - end - - local enabled_groups = dynamic_hook.enabled_groups - if not enabled_groups[group_name] then - return original_func(%s) - end - end - - if handlers.before_mut then - local ok - ok, %s = pcall(handlers.before_mut, %s) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run before_mut hook of %%s: %%s", - group_name, a0)) - end - end - - if handlers.befores then - for _, func in ipairs(handlers.befores) do - local ok, err = pcall(func, %s) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run before hook of %%s: %%s", - group_name, err)) - end - end - end - - local r0, r1, r2, r3, r4, r5, r6, r7 = original_func(%s) - - if handlers.after_mut then - local ok, err = pcall(handlers.after_mut, r0, r1, r2, r3, r4, r5, r6, r7) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run after_mut hook of %%s: %%s", - group_name, err)) - end - end - - if handlers.afters then - for _, func in ipairs(handlers.afters) do - local ok, err = pcall(func, r0, r1, r2, r3, r4, r5, r6, r7) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run after hook of %%s: %%s", - group_name, err)) - end - end - end - - return r0, r1, r2, r3, r4, r5, r6, r7 - end - end -]] - - -local _M = {} - - -local function warp_function_0(always_enabled_groups, group_name, original_func, handlers) - return function() - if not always_enabled_groups[group_name] then - local phase = ngx_get_phase() - if phase == "init" or phase == "init_worker" then - return original_func() - end - - local dynamic_hook = ngx.ctx.dynamic_hook - if not dynamic_hook then - return original_func() - end - - local enabled_groups = dynamic_hook.enabled_groups - if not enabled_groups[group_name] then - return original_func() - end - end - - if handlers.before_mut then - local ok, err = pcall(handlers.before_mut) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run before_mut hook of %s: %s", - group_name, err)) - end - end - - if handlers.befores then - for _, func in ipairs(handlers.befores) do - local ok, err = pcall(func) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run before hook of %s: %s", - group_name, err)) - end - end - end - - local r0, r1, r2, r3, r4, r5, r6, r7 = original_func() - - if handlers.after_mut then - local ok, err = pcall(handlers.after_mut, r0, r1, r2, r3, r4, r5, r6, r7) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run after_mut hook of %s: %s", - group_name, err)) - end - end - - if handlers.afters then - for _, func in ipairs(handlers.afters) do - local ok, err = pcall(func, r0, r1, r2, r3, r4, r5, r6, r7) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run after hook of %s: %s", - group_name, err)) - end - end - end - - return r0, r1, r2, r3, r4, r5, r6, r7 - end -end - - -local function wrap_function_varargs(always_enabled_groups, group_name, original_func, handlers) - return function(...) - if not always_enabled_groups[group_name] then - local phase = ngx_get_phase() - if phase == "init" or phase == "init_worker" then - return original_func(...) - end - - local dynamic_hook = ngx.ctx.dynamic_hook - if not dynamic_hook then - return original_func(...) - end - - local enabled_groups = dynamic_hook.enabled_groups - if not enabled_groups[group_name] then - return original_func(...) - end - end - - -- before_mut is not supported for varargs functions - - if handlers.befores then - for _, func in ipairs(handlers.befores) do - local ok, err = pcall(func, ...) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run before hook of %s: %s", - group_name, err)) - end - end - end - - local r0, r1, r2, r3, r4, r5, r6, r7 = original_func(...) - - if handlers.after_mut then - local ok, err = pcall(handlers.after_mut, r0, r1, r2, r3, r4, r5, r6, r7) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run after_mut hook of %s: %s", - group_name, err)) - end - end - - if handlers.afters then - for _, func in ipairs(handlers.afters) do - local ok, err = pcall(func, r0, r1, r2, r3, r4, r5, r6, r7) - if not ok then - ngx.log(ngx.WARN, - string.format("failed to run after hook of %s: %s", - group_name, err)) - end - end - end - - return r0, r1, r2, r3, r4, r5, r6, r7 - end -end - - -function _M.generate_wrap_function(max_args) - if max_args == 0 then - return warp_function_0 - end - - if max_args == "varargs" then - return wrap_function_varargs - end - - local args = "a0" -- the 1st arg must be named as "a0" as - -- it will be used in the error log - - for i = 1, max_args - 1 do - args = args .. ", a" .. i - end - - local func = assert(loadstring(string.format(TEMPLATE, args, args, args, args, args, args, args, args)))() - assert(type(func) == "function", "failed to generate wrap function: " .. tostring(func)) - return func -end - -return _M \ No newline at end of file diff --git a/kong/global.lua b/kong/global.lua index ace19ae87fb..468f55bf821 100644 --- a/kong/global.lua +++ b/kong/global.lua @@ -249,16 +249,17 @@ function _GLOBAL.init_cache(kong_config, cluster_events, worker_events) end return kong_cache.new({ - shm_name = "kong_db_cache", - cluster_events = cluster_events, - worker_events = worker_events, - ttl = db_cache_ttl, - neg_ttl = db_cache_neg_ttl or db_cache_ttl, - resurrect_ttl = kong_config.resurrect_ttl, - page = page, - cache_pages = cache_pages, - resty_lock_opts = LOCK_OPTS, - lru_size = get_lru_size(kong_config), + shm_name = "kong_db_cache", + cluster_events = cluster_events, + worker_events = worker_events, + ttl = db_cache_ttl, + neg_ttl = db_cache_neg_ttl or db_cache_ttl, + resurrect_ttl = kong_config.resurrect_ttl, + page = page, + cache_pages = cache_pages, + resty_lock_opts = LOCK_OPTS, + lru_size = get_lru_size(kong_config), + invalidation_channel = "invalidations", }) end diff --git a/kong/init.lua b/kong/init.lua index d37a08325a0..2c837dd0e52 100644 --- a/kong/init.lua +++ b/kong/init.lua @@ -322,7 +322,7 @@ local function execute_global_plugins_iterator(plugins_iterator, phase, ctx) local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:plugin_iterator") + req_dyn_hook_run_hooks("timing", "before:plugin_iterator") end for _, plugin, configuration in iterator, plugins, 0 do @@ -334,13 +334,13 @@ local function execute_global_plugins_iterator(plugins_iterator, phase, ctx) setup_plugin_context(ctx, plugin, configuration) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:plugin", plugin.name, ctx.plugin_id) + req_dyn_hook_run_hooks("timing", "before:plugin", plugin.name, ctx.plugin_id) end plugin.handler[phase](plugin.handler, configuration) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:plugin") + req_dyn_hook_run_hooks("timing", "after:plugin") end reset_plugin_context(ctx, old_ws) @@ -351,7 +351,7 @@ local function execute_global_plugins_iterator(plugins_iterator, phase, ctx) end if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:plugin_iterator") + req_dyn_hook_run_hooks("timing", "after:plugin_iterator") end end @@ -372,7 +372,7 @@ local function execute_collecting_plugins_iterator(plugins_iterator, phase, ctx) local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:plugin_iterator") + req_dyn_hook_run_hooks("timing", "before:plugin_iterator") end for _, plugin, configuration in iterator, plugins, 0 do @@ -385,14 +385,14 @@ local function execute_collecting_plugins_iterator(plugins_iterator, phase, ctx) setup_plugin_context(ctx, plugin, configuration) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:plugin", plugin.name, ctx.plugin_id) + req_dyn_hook_run_hooks( "timing", "before:plugin", plugin.name, ctx.plugin_id) end local co = coroutine.create(plugin.handler[phase]) local cok, cerr = coroutine.resume(co, plugin.handler, configuration) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:plugin") + req_dyn_hook_run_hooks("timing", "after:plugin") end if not cok then @@ -422,7 +422,7 @@ local function execute_collecting_plugins_iterator(plugins_iterator, phase, ctx) end if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:plugin_iterator") + req_dyn_hook_run_hooks("timing", "after:plugin_iterator") end ctx.delay_response = nil @@ -443,7 +443,7 @@ local function execute_collected_plugins_iterator(plugins_iterator, phase, ctx) local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:plugin_iterator") + req_dyn_hook_run_hooks("timing", "before:plugin_iterator") end for _, plugin, configuration in iterator, plugins, 0 do @@ -455,13 +455,13 @@ local function execute_collected_plugins_iterator(plugins_iterator, phase, ctx) setup_plugin_context(ctx, plugin, configuration) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:plugin", plugin.name, ctx.plugin_id) + req_dyn_hook_run_hooks("timing", "before:plugin", plugin.name, ctx.plugin_id) end plugin.handler[phase](plugin.handler, configuration) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:plugin") + req_dyn_hook_run_hooks("timing", "after:plugin") end reset_plugin_context(ctx, old_ws) @@ -472,7 +472,7 @@ local function execute_collected_plugins_iterator(plugins_iterator, phase, ctx) end if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:plugin_iterator") + req_dyn_hook_run_hooks("timing", "after:plugin_iterator") end end @@ -1087,7 +1087,7 @@ function Kong.rewrite() ctx.KONG_PHASE = PHASES.rewrite local has_timing - req_dyn_hook_run_hooks(ctx, "timing:auth", "auth") + req_dyn_hook_run_hooks("timing:auth", "auth") if req_dyn_hook_is_group_enabled("timing") then ctx.has_timing = true @@ -1095,7 +1095,7 @@ function Kong.rewrite() end if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:rewrite") + req_dyn_hook_run_hooks("timing", "before:rewrite") end kong_resty_ctx.stash_ref(ctx) @@ -1124,7 +1124,7 @@ function Kong.rewrite() ctx.KONG_REWRITE_TIME = ctx.KONG_REWRITE_ENDED_AT - ctx.KONG_REWRITE_START if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:rewrite") + req_dyn_hook_run_hooks("timing", "after:rewrite") end end @@ -1134,7 +1134,7 @@ function Kong.access() local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:access") + req_dyn_hook_run_hooks("timing", "before:access") end if not ctx.KONG_ACCESS_START then @@ -1160,7 +1160,7 @@ function Kong.access() ctx.KONG_RESPONSE_LATENCY = ctx.KONG_ACCESS_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:access") + req_dyn_hook_run_hooks("timing", "after:access") end return flush_delayed_response(ctx) @@ -1176,7 +1176,7 @@ function Kong.access() ctx.buffered_proxying = nil if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:access") + req_dyn_hook_run_hooks("timing", "after:access") end return kong.response.error(503, "no Service found with those values") @@ -1197,7 +1197,7 @@ function Kong.access() local upgrade = var.upstream_upgrade or "" if version < 2 and upgrade == "" then if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:access") + req_dyn_hook_run_hooks("timing", "after:access") end return Kong.response() @@ -1213,7 +1213,7 @@ function Kong.access() end if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:access") + req_dyn_hook_run_hooks("timing", "after:access") end end @@ -1223,7 +1223,7 @@ function Kong.balancer() local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:balancer") + req_dyn_hook_run_hooks("timing", "before:balancer") end -- This may be called multiple times, and no yielding here! @@ -1305,7 +1305,7 @@ function Kong.balancer() ctx.KONG_PROXY_LATENCY = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:balancer") + req_dyn_hook_run_hooks("timing", "after:balancer") end return ngx.exit(errcode) @@ -1317,7 +1317,7 @@ function Kong.balancer() ngx_log(ngx_ERR, "failed to set balancer Host header: ", err) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:balancer") + req_dyn_hook_run_hooks("timing", "after:balancer") end return ngx.exit(500) @@ -1372,7 +1372,7 @@ function Kong.balancer() ctx.KONG_PROXY_LATENCY = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:balancer") + req_dyn_hook_run_hooks("timing", "after:balancer") end return ngx.exit(500) @@ -1412,7 +1412,7 @@ function Kong.balancer() ctx.KONG_PROXY_LATENCY = ctx.KONG_BALANCER_ENDED_AT - ctx.KONG_PROCESSING_START if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:balancer") + req_dyn_hook_run_hooks("timing", "after:balancer") end end @@ -1441,7 +1441,7 @@ do local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:response") + req_dyn_hook_run_hooks("timing", "before:response") end local plugins_iterator = runloop.get_plugins_iterator() @@ -1462,7 +1462,7 @@ do ngx.status = res.status or 502 if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:response") + req_dyn_hook_run_hooks("timing", "after:response") end return kong_error_handlers(ctx) @@ -1516,7 +1516,7 @@ do ngx.print(body) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:response") + req_dyn_hook_run_hooks("timing", "after:response") end -- jump over the balancer to header_filter @@ -1530,7 +1530,7 @@ function Kong.header_filter() local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:header_filter") + req_dyn_hook_run_hooks("timing", "before:header_filter") end if not ctx.KONG_PROCESSING_START then @@ -1602,7 +1602,7 @@ function Kong.header_filter() ctx.KONG_HEADER_FILTER_TIME = ctx.KONG_HEADER_FILTER_ENDED_AT - ctx.KONG_HEADER_FILTER_START if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:header_filter") + req_dyn_hook_run_hooks("timing", "after:header_filter") end end @@ -1612,7 +1612,7 @@ function Kong.body_filter() local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:body_filter") + req_dyn_hook_run_hooks("timing", "before:body_filter") end if not ctx.KONG_BODY_FILTER_START then @@ -1671,7 +1671,7 @@ function Kong.body_filter() if not arg[2] then if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:body_filter") + req_dyn_hook_run_hooks("timing", "after:body_filter") end return @@ -1693,7 +1693,7 @@ function Kong.body_filter() end if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:body_filter") + req_dyn_hook_run_hooks("timing", "after:body_filter") end end @@ -1703,7 +1703,7 @@ function Kong.log() local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:log") + req_dyn_hook_run_hooks("timing", "before:log") end if not ctx.KONG_LOG_START then @@ -1798,7 +1798,7 @@ function Kong.log() runloop.log.after(ctx) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:log") + req_dyn_hook_run_hooks("timing", "after:log") end release_table(CTX_NS, ctx) diff --git a/kong/pdk/init.lua b/kong/pdk/init.lua index 92d10c59029..1533162fe3f 100644 --- a/kong/pdk/init.lua +++ b/kong/pdk/init.lua @@ -32,7 +32,7 @@ -- -- @field kong.version_num -- @usage --- if kong.version_num < 13000 then -- 000.130.00 -> 0.13.0 +-- if kong.version_num < 3004001 then -- 300.40.1 -> 3.4.1 -- -- no support for Routes & Services -- end diff --git a/kong/plugins/prometheus/schema.lua b/kong/plugins/prometheus/schema.lua index e210b67856d..9b067e3bf87 100644 --- a/kong/plugins/prometheus/schema.lua +++ b/kong/plugins/prometheus/schema.lua @@ -18,9 +18,9 @@ return { fields = { { per_consumer = { description = "A boolean value that determines if per-consumer metrics should be collected. If enabled, the `kong_http_requests_total` and `kong_bandwidth_bytes` metrics fill in the consumer label when available.", type = "boolean", default = false }, }, { status_code_metrics = { description = "A boolean value that determines if status code metrics should be collected. If enabled, `http_requests_total`, `stream_sessions_total` metrics will be exported.", type = "boolean", default = false }, }, - { latency_metrics = { description = "A boolean value that determines if status code metrics should be collected. If enabled, `kong_latency_ms`, `upstream_latency_ms` and `request_latency_ms` metrics will be exported.", type = "boolean", default = false }, }, - { bandwidth_metrics = { description = "A boolean value that determines if status code metrics should be collected. If enabled, `bandwidth_bytes` and `stream_sessions_total` metrics will be exported.", type = "boolean", default = false }, }, - { upstream_health_metrics = { description = "A boolean value that determines if status code metrics should be collected. If enabled, `upstream_target_health` metric will be exported.", type = "boolean", default = false }, }, + { latency_metrics = { description = "A boolean value that determines if latency metrics should be collected. If enabled, `kong_latency_ms`, `upstream_latency_ms` and `request_latency_ms` metrics will be exported.", type = "boolean", default = false }, }, + { bandwidth_metrics = { description = "A boolean value that determines if bandwidth metrics should be collected. If enabled, `bandwidth_bytes` and `stream_sessions_total` metrics will be exported.", type = "boolean", default = false }, }, + { upstream_health_metrics = { description = "A boolean value that determines if upstream metrics should be collected. If enabled, `upstream_target_health` metric will be exported.", type = "boolean", default = false }, }, }, custom_validator = validate_shared_dict, }, }, diff --git a/kong/resty/dns/client.lua b/kong/resty/dns/client.lua index fcc92a4217d..78cf91d29b5 100644 --- a/kong/resty/dns/client.lua +++ b/kong/resty/dns/client.lua @@ -143,7 +143,7 @@ local cachelookup = function(qname, qtype) local ctx = ngx.ctx if ctx and ctx.has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "dns:cache_lookup", cached ~= nil) + req_dyn_hook_run_hooks("timing", "dns:cache_lookup", cached ~= nil) end if cached then diff --git a/kong/router/atc.lua b/kong/router/atc.lua index b186a1b29bb..3fed56771c4 100644 --- a/kong/router/atc.lua +++ b/kong/router/atc.lua @@ -2,10 +2,10 @@ local _M = {} local _MT = { __index = _M, } -local buffer = require("string.buffer") local lrucache = require("resty.lrucache") local tb_new = require("table.new") local utils = require("kong.router.utils") +local transform = require("kong.router.transform") local rat = require("kong.tools.request_aware_table") local yield = require("kong.tools.yield").yield @@ -15,9 +15,6 @@ local assert = assert local setmetatable = setmetatable local pairs = pairs local ipairs = ipairs -local tonumber = tonumber - - local max = math.max @@ -32,22 +29,15 @@ local ngx_ERR = ngx.ERR local check_select_params = utils.check_select_params local get_service_info = utils.get_service_info local route_match_stat = utils.route_match_stat +local split_host_port = transform.split_host_port local DEFAULT_MATCH_LRUCACHE_SIZE = utils.DEFAULT_MATCH_LRUCACHE_SIZE -local LOGICAL_OR = " || " -local LOGICAL_AND = " && " - - local is_http = ngx.config.subsystem == "http" --- reuse buffer object -local values_buf = buffer.new(64) - - local get_atc_context local get_atc_router local get_atc_fields @@ -129,67 +119,6 @@ do end -local is_empty_field -do - local null = ngx.null - local isempty = require("table.isempty") - - is_empty_field = function(f) - return f == nil or f == null or isempty(f) - end -end - - -local function escape_str(str) - -- raw string - if not str:find([["#]], 1, true) then - return "r#\"" .. str .. "\"#" - end - - -- standard string escaping (unlikely case) - if str:find([[\]], 1, true) then - str = str:gsub([[\]], [[\\]]) - end - - if str:find([["]], 1, true) then - str = str:gsub([["]], [[\"]]) - end - - return "\"" .. str .. "\"" -end - - -local function gen_for_field(name, op, vals, val_transform) - if is_empty_field(vals) then - return nil - end - - local vals_n = #vals - assert(vals_n > 0) - - values_buf:reset():put("(") - - for i = 1, vals_n do - local p = vals[i] - local op = (type(op) == "string") and op or op(p) - - if i > 1 then - values_buf:put(LOGICAL_OR) - end - - values_buf:putf("%s %s %s", name, op, - escape_str(val_transform and val_transform(op, p) or p)) - end - - -- consume the whole buffer - -- returns a local variable instead of using a tail call - -- to avoid NYI - local str = values_buf:put(")"):get() - - return str -end - - local function add_atc_matcher(inst, route, route_id, get_exp_and_priority, remove_existing) @@ -371,48 +300,6 @@ function _M.new(routes, cache, cache_neg, old_router, get_exp_and_priority) end --- split port in host, ignore form '[...]' --- example.com:123 => example.com, 123 --- example.*:123 => example.*, 123 -local split_host_port -do - local DEFAULT_HOSTS_LRUCACHE_SIZE = DEFAULT_MATCH_LRUCACHE_SIZE - - local memo_hp = lrucache.new(DEFAULT_HOSTS_LRUCACHE_SIZE) - - split_host_port = function(key) - if not key then - return nil, nil - end - - local m = memo_hp:get(key) - - if m then - return m[1], m[2] - end - - local p = key:find(":", nil, true) - if not p then - memo_hp:set(key, { key, nil }) - return key, nil - end - - local port = tonumber(key:sub(p + 1)) - - if not port then - memo_hp:set(key, { key, nil }) - return key, nil - end - - local host = key:sub(1, p - 1) - - memo_hp:set(key, { host, port }) - - return host, port - end -end - - local CACHE_PARAMS @@ -586,6 +473,7 @@ function _M:exec(ctx) return match_t end + else -- is stream subsystem @@ -708,16 +596,8 @@ function _M:exec(ctx) return match_t end -end -- if is_http - -_M.LOGICAL_OR = LOGICAL_OR -_M.LOGICAL_AND = LOGICAL_AND - -_M.escape_str = escape_str -_M.is_empty_field = is_empty_field -_M.gen_for_field = gen_for_field -_M.split_host_port = split_host_port +end -- if is_http return _M diff --git a/kong/router/compat.lua b/kong/router/compat.lua index df4285f21db..410168e8575 100644 --- a/kong/router/compat.lua +++ b/kong/router/compat.lua @@ -1,27 +1,21 @@ local _M = {} -local bit = require("bit") -local buffer = require("string.buffer") local atc = require("kong.router.atc") local utils = require("kong.router.utils") +local transform = require("kong.router.transform") local tb_new = require("table.new") local tb_nkeys = require("table.nkeys") local uuid = require("resty.jit-uuid") -local shallow_copy = require("kong.tools.utils").shallow_copy -local replace_dashes_lower = require("kong.tools.string").replace_dashes_lower +local shallow_copy = require("kong.tools.utils").shallow_copy local is_regex_magic = utils.is_regex_magic -local parse_ip_addr = utils.parse_ip_addr - - -local escape_str = atc.escape_str -local is_empty_field = atc.is_empty_field -local gen_for_field = atc.gen_for_field -local split_host_port = atc.split_host_port +local is_empty_field = transform.is_empty_field +local get_expression = transform.get_expression +local get_priority = transform.get_priority local type = type @@ -29,463 +23,17 @@ local pairs = pairs local ipairs = ipairs local assert = assert local tb_insert = table.insert -local byte = string.byte -local bor, band, lshift = bit.bor, bit.band, bit.lshift local is_http = ngx.config.subsystem == "http" -local DOT = byte(".") -local TILDE = byte("~") -local ASTERISK = byte("*") -local MAX_HEADER_COUNT = 255 - - --- reuse buffer objects -local expr_buf = buffer.new(128) -local hosts_buf = buffer.new(64) -local headers_buf = buffer.new(128) -local single_header_buf = buffer.new(64) - - --- sep: a seperator of expressions, like '&&' --- idx: indicate whether or not to add 'sep' --- for example, we should not add 'sep' for the first element in array -local function expression_append(buf, sep, str, idx) - if #buf > 0 and - (idx == nil or idx > 1) - then - buf:put(sep) - end - buf:put(str) -end - - -local OP_EQUAL = "==" -local OP_PREFIX = "^=" -local OP_POSTFIX = "=^" -local OP_REGEX = "~" -local OP_IN = "in" - - -local LOGICAL_OR = atc.LOGICAL_OR -local LOGICAL_AND = atc.LOGICAL_AND - - -- When splitting routes, we need to assign new UUIDs to the split routes. We use uuid v5 to generate them from -- the original route id and the path index so that incremental rebuilds see stable IDs for routes that have not -- changed. local uuid_generator = assert(uuid.factory_v5('7f145bf9-0dce-4f91-98eb-debbce4b9f6b')) -local function gen_for_nets(ip_field, port_field, vals) - if is_empty_field(vals) then - return nil - end - - local nets_buf = buffer.new(64):put("(") - - for i = 1, #vals do - local v = vals[i] - - if type(v) ~= "table" then - ngx.log(ngx.ERR, "sources/destinations elements must be a table") - return nil - end - - if is_empty_field(v) then - ngx.log(ngx.ERR, "sources/destinations elements must not be empty") - return nil - end - - local ip = v.ip - local port = v.port - - local exp_ip, exp_port - - if ip then - local addr, mask = parse_ip_addr(ip) - - if mask then -- ip in cidr - exp_ip = ip_field .. " " .. OP_IN .. " " .. - addr .. "/" .. mask - - else -- ip == addr - exp_ip = ip_field .. " " .. OP_EQUAL .. " " .. - addr - end - end - - if port then - exp_port = port_field .. " " .. OP_EQUAL .. " " .. port - end - - if not ip then - expression_append(nets_buf, LOGICAL_OR, exp_port, i) - goto continue - end - - if not port then - expression_append(nets_buf, LOGICAL_OR, exp_ip, i) - goto continue - end - - expression_append(nets_buf, LOGICAL_OR, - "(" .. exp_ip .. LOGICAL_AND .. exp_port .. ")", i) - - ::continue:: - end -- for - - local str = nets_buf:put(")"):get() - - -- returns a local variable instead of using a tail call - -- to avoid NYI - return str -end - - -local function get_expression(route) - local methods = route.methods - local hosts = route.hosts - local paths = route.paths - local headers = route.headers - local snis = route.snis - - local srcs = route.sources - local dsts = route.destinations - - expr_buf:reset() - - local gen = gen_for_field("tls.sni", OP_EQUAL, snis, function(_, p) - if #p > 1 and byte(p, -1) == DOT then - -- last dot in FQDNs must not be used for routing - return p:sub(1, -2) - end - - return p - end) - if gen then - -- See #6425, if `net.protocol` is not `https` - -- then SNI matching should simply not be considered - if srcs or dsts then - gen = "(net.protocol != r#\"tls\"#" .. LOGICAL_OR .. gen .. ")" - else - gen = "(net.protocol != r#\"https\"#" .. LOGICAL_OR .. gen .. ")" - end - - expression_append(expr_buf, LOGICAL_AND, gen) - end - - -- stream expression - - do - local src_gen = gen_for_nets("net.src.ip", "net.src.port", srcs) - local dst_gen = gen_for_nets("net.dst.ip", "net.dst.port", dsts) - - if src_gen then - expression_append(expr_buf, LOGICAL_AND, src_gen) - end - - if dst_gen then - expression_append(expr_buf, LOGICAL_AND, dst_gen) - end - - if src_gen or dst_gen then - -- returns a local variable instead of using a tail call - -- to avoid NYI - local str = expr_buf:get() - return str - end - end - - -- http expression - - local gen = gen_for_field("http.method", OP_EQUAL, methods) - if gen then - expression_append(expr_buf, LOGICAL_AND, gen) - end - - if not is_empty_field(hosts) then - hosts_buf:reset():put("(") - - for i, h in ipairs(hosts) do - local host, port = split_host_port(h) - - local op = OP_EQUAL - if byte(host) == ASTERISK then - -- postfix matching - op = OP_POSTFIX - host = host:sub(2) - - elseif byte(host, -1) == ASTERISK then - -- prefix matching - op = OP_PREFIX - host = host:sub(1, -2) - end - - local exp = "http.host ".. op .. " r#\"" .. host .. "\"#" - if port then - exp = "(" .. exp .. LOGICAL_AND .. - "net.dst.port ".. OP_EQUAL .. " " .. port .. ")" - end - expression_append(hosts_buf, LOGICAL_OR, exp, i) - end -- for route.hosts - - expression_append(expr_buf, LOGICAL_AND, - hosts_buf:put(")"):get()) - end - - gen = gen_for_field("http.path", function(path) - return is_regex_magic(path) and OP_REGEX or OP_PREFIX - end, paths, function(op, p) - if op == OP_REGEX then - -- 1. strip leading `~` - -- 2. prefix with `^` to match the anchored behavior of the traditional router - -- 3. update named capture opening tag for rust regex::Regex compatibility - return "^" .. p:sub(2):gsub("?<", "?P<") - end - - return p - end) - if gen then - expression_append(expr_buf, LOGICAL_AND, gen) - end - - if not is_empty_field(headers) then - headers_buf:reset() - - for h, v in pairs(headers) do - single_header_buf:reset():put("(") - - for i, value in ipairs(v) do - local name = "any(lower(http.headers." .. replace_dashes_lower(h) .. "))" - local op = OP_EQUAL - - -- value starts with "~*" - if byte(value, 1) == TILDE and byte(value, 2) == ASTERISK then - value = value:sub(3) - op = OP_REGEX - end - - expression_append(single_header_buf, LOGICAL_OR, - name .. " " .. op .. " " .. escape_str(value:lower()), i) - end - - expression_append(headers_buf, LOGICAL_AND, - single_header_buf:put(")"):get()) - end - - expression_append(expr_buf, LOGICAL_AND, headers_buf:get()) - end - - local str = expr_buf:get() - - -- returns a local variable instead of using a tail call - -- to avoid NYI - return str -end - - -local lshift_uint64 -do - local ffi = require("ffi") - local ffi_uint = ffi.new("uint64_t") - - lshift_uint64 = function(v, offset) - ffi_uint = v - return lshift(ffi_uint, offset) - end -end - - -local stream_get_priority -do - -- compatible with http priority - local STREAM_SNI_BIT = lshift_uint64(0x01ULL, 61) - - -- IP > PORT > CIDR - local IP_BIT = lshift_uint64(0x01ULL, 3) - local PORT_BIT = lshift_uint64(0x01ULL, 2) - local CIDR_BIT = lshift_uint64(0x01ULL, 0) - - local function calc_ip_weight(ips) - local weight = 0x0ULL - - if is_empty_field(ips) then - return weight - end - - for i = 1, #ips do - local ip = ips[i].ip - local port = ips[i].port - - if ip then - if ip:find("/", 1, true) then - weight = bor(weight, CIDR_BIT) - - else - weight = bor(weight, IP_BIT) - end - end - - if port then - weight = bor(weight, PORT_BIT) - end - end - - return weight - end - - stream_get_priority = function(snis, srcs, dsts) - local match_weight = 0x0ULL - - -- [sni] has higher priority than [src] or [dst] - if not is_empty_field(snis) then - match_weight = STREAM_SNI_BIT - end - - -- [src] + [dst] has higher priority than [sni] - if not is_empty_field(srcs) and - not is_empty_field(dsts) - then - match_weight = STREAM_SNI_BIT - end - - local src_bits = calc_ip_weight(srcs) - local dst_bits = calc_ip_weight(dsts) - - local priority = bor(match_weight, - lshift(src_bits, 4), - dst_bits) - - return priority - end -end - - -local PLAIN_HOST_ONLY_BIT = lshift_uint64(0x01ULL, 60) -local REGEX_URL_BIT = lshift_uint64(0x01ULL, 51) - - --- convert a route to a priority value for use in the ATC router --- priority must be a 64-bit non negative integer --- format (big endian): --- 0 1 2 3 --- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 --- +-----+-+---------------+-+-------------------------------------+ --- | W |P| Header |R| Regex | --- | G |L| |G| Priority | --- | T |N| Count |X| | --- +-----+-+-----------------+-------------------------------------+ --- | Regex Priority | Max Length | --- | (cont) | | --- | | | --- +-------------------------+-------------------------------------+ -local function get_priority(route) - local snis = route.snis - local srcs = route.sources - local dsts = route.destinations - - -- stream expression - - if not is_empty_field(srcs) or - not is_empty_field(dsts) - then - return stream_get_priority(snis, srcs, dsts) - end - - -- http expression - - local methods = route.methods - local hosts = route.hosts - local paths = route.paths - local headers = route.headers - - local match_weight = 0 -- 0x0ULL - - if not is_empty_field(methods) then - match_weight = match_weight + 1 - end - - if not is_empty_field(hosts) then - match_weight = match_weight + 1 - end - - local headers_count = is_empty_field(headers) and 0 or tb_nkeys(headers) - - if headers_count > 0 then - match_weight = match_weight + 1 - - if headers_count > MAX_HEADER_COUNT then - ngx.log(ngx.WARN, "too many headers in route ", route.id, - " headers count capped at ", MAX_HEADER_COUNT, - " when sorting") - headers_count = MAX_HEADER_COUNT - end - end - - if not is_empty_field(snis) then - match_weight = match_weight + 1 - end - - local plain_host_only = type(hosts) == "table" - - if plain_host_only then - for _, h in ipairs(hosts) do - if h:find("*", nil, true) then - plain_host_only = false - break - end - end - end - - local uri_length = 0 - local regex_url = false - - if not is_empty_field(paths) then - match_weight = match_weight + 1 - - local p = paths[1] - - if is_regex_magic(p) then - regex_url = true - - else - uri_length = #p - end - - for i = 2, #paths do - p = paths[i] - - if regex_url then - assert(is_regex_magic(p), - "cannot mix regex and non-regex paths in get_priority()") - - else - assert(#p == uri_length, - "cannot mix different length prefixes in get_priority()") - end - end - end - - local match_weight = lshift_uint64(match_weight, 61) - local headers_count = lshift_uint64(headers_count, 52) - - local regex_priority = lshift_uint64(regex_url and route.regex_priority or 0, 19) - local max_length = band(uri_length, 0x7FFFF) - - local priority = bor(match_weight, - plain_host_only and PLAIN_HOST_ONLY_BIT or 0, - regex_url and REGEX_URL_BIT or 0, - headers_count, - regex_priority, - max_length) - - return priority -end - - local function get_exp_and_priority(route) if route.expression then ngx.log(ngx.ERR, "expecting a traditional route while it's not (probably an expressions route). ", diff --git a/kong/router/expressions.lua b/kong/router/expressions.lua index 129689f1313..733aaeb88c6 100644 --- a/kong/router/expressions.lua +++ b/kong/router/expressions.lua @@ -5,15 +5,16 @@ local re_gsub = ngx.re.gsub local atc = require("kong.router.atc") -local gen_for_field = atc.gen_for_field +local transform = require("kong.router.transform") -local OP_EQUAL = "==" -local NET_PORT_REG = [[(net\.port)(\s*)([=> example.com, 123 +-- example.*:123 => example.*, 123 +local split_host_port +do + local tonumber = tonumber + + local DEFAULT_HOSTS_LRUCACHE_SIZE = utils.DEFAULT_MATCH_LRUCACHE_SIZE + + local memo_hp = lrucache.new(DEFAULT_HOSTS_LRUCACHE_SIZE) + + split_host_port = function(key) + if not key then + return nil, nil + end + + local m = memo_hp:get(key) + + if m then + return m[1], m[2] + end + + local p = key:find(":", nil, true) + if not p then + memo_hp:set(key, { key, nil }) + return key, nil + end + + local port = tonumber(key:sub(p + 1)) + + if not port then + memo_hp:set(key, { key, nil }) + return key, nil + end + + local host = key:sub(1, p - 1) + + memo_hp:set(key, { host, port }) + + return host, port + end +end + + +local LOGICAL_OR = " || " +local LOGICAL_AND = " && " + + +local OP_EQUAL = "==" +local OP_PREFIX = "^=" +local OP_POSTFIX = "=^" +local OP_REGEX = "~" +local OP_IN = "in" + + +local DOT = byte(".") +local TILDE = byte("~") +local ASTERISK = byte("*") + + +-- reuse buffer objects +local values_buf = buffer.new(64) +local nets_buf = buffer.new(64) +local expr_buf = buffer.new(64) +local hosts_buf = buffer.new(64) +local headers_buf = buffer.new(64) +local single_header_buf = buffer.new(64) + + +-- sep: a seperator of expressions, like '&&' +-- idx: indicate whether or not to add 'sep' +-- for example, we should not add 'sep' for the first element in array +local function expression_append(buf, sep, str, idx) + if #buf > 0 and + (idx == nil or idx > 1) + then + buf:put(sep) + end + buf:put(str) +end + + +local function gen_for_field(name, op, vals, val_transform) + if is_empty_field(vals) then + return nil + end + + local vals_n = #vals + assert(vals_n > 0) + + values_buf:reset():put("(") + + for i = 1, vals_n do + local p = vals[i] + local op = (type(op) == "string") and op or op(p) + + local expr = fmt("%s %s %s", name, op, + escape_str(val_transform and val_transform(op, p) or p)) + + expression_append(values_buf, LOGICAL_OR, expr, i) + end + + -- consume the whole buffer + -- returns a local variable instead of using a tail call + -- to avoid NYI + local str = values_buf:put(")"):get() + + return str +end + + +local function parse_ip_addr(ip) + local addr, mask = ipmatcher.split_ip(ip) + + if not mask then + return addr + end + + local ipv4 = ipmatcher.parse_ipv4(addr) + + -- FIXME: support ipv6 + if not ipv4 then + return addr, mask + end + + local cidr = lshift(rshift(ipv4, 32 - mask), 32 - mask) + + local n1 = band( cidr , 0xff) + local n2 = band(rshift(cidr, 8), 0xff) + local n3 = band(rshift(cidr, 16), 0xff) + local n4 = band(rshift(cidr, 24), 0xff) + + return n4 .. "." .. n3 .. "." .. n2 .. "." .. n1, mask +end + + +local function gen_for_nets(ip_field, port_field, vals) + if is_empty_field(vals) then + return nil + end + + nets_buf:reset():put("(") + + for i = 1, #vals do + local v = vals[i] + + if type(v) ~= "table" then + ngx.log(ngx.ERR, "sources/destinations elements must be a table") + return nil + end + + if is_empty_field(v) then + ngx.log(ngx.ERR, "sources/destinations elements must not be empty") + return nil + end + + local ip = v.ip + local port = v.port + + local exp_ip, exp_port + + if not is_null(ip) then + local addr, mask = parse_ip_addr(ip) + + if mask then -- ip in cidr + exp_ip = ip_field .. " " .. OP_IN .. " " .. + addr .. "/" .. mask + + else -- ip == addr + exp_ip = ip_field .. " " .. OP_EQUAL .. " " .. + addr + end + end + + if not is_null(port) then + exp_port = port_field .. " " .. OP_EQUAL .. " " .. port + end + + -- only add port expression + if is_null(ip) then + expression_append(nets_buf, LOGICAL_OR, exp_port, i) + goto continue + end + + -- only add ip address expression + if is_null(port) then + expression_append(nets_buf, LOGICAL_OR, exp_ip, i) + goto continue + end + + -- add port and ip address expression with '()' + expression_append(nets_buf, LOGICAL_OR, + "(" .. exp_ip .. LOGICAL_AND .. exp_port .. ")", i) + + ::continue:: + end -- for + + local str = nets_buf:put(")"):get() + + -- returns a local variable instead of using a tail call + -- to avoid NYI + return str +end + + +local is_stream_route +do + local is_stream_protocol = { + tcp = true, + udp = true, + tls = true, + tls_passthrough = true, + } + + is_stream_route = function(r) + if not r.protocols then + return false + end + + return is_stream_protocol[r.protocols[1]] + end +end + + +local function get_expression(route) + local methods = route.methods + local hosts = route.hosts + local paths = route.paths + local headers = route.headers + local snis = route.snis + + local srcs = route.sources + local dsts = route.destinations + + expr_buf:reset() + + local gen = gen_for_field("tls.sni", OP_EQUAL, snis, function(_, p) + if #p > 1 and byte(p, -1) == DOT then + -- last dot in FQDNs must not be used for routing + return p:sub(1, -2) + end + + return p + end) + if gen then + -- See #6425, if `net.protocol` is not `https` + -- then SNI matching should simply not be considered + if is_stream_route(route) then + gen = "(net.protocol != r#\"tls\"#" .. LOGICAL_OR .. gen .. ")" + else + gen = "(net.protocol != r#\"https\"#" .. LOGICAL_OR .. gen .. ")" + end + + expression_append(expr_buf, LOGICAL_AND, gen) + end + + -- now http route support net.src.* and net.dst.* + + local src_gen = gen_for_nets("net.src.ip", "net.src.port", srcs) + local dst_gen = gen_for_nets("net.dst.ip", "net.dst.port", dsts) + + if src_gen then + expression_append(expr_buf, LOGICAL_AND, src_gen) + end + + if dst_gen then + expression_append(expr_buf, LOGICAL_AND, dst_gen) + end + + -- stream expression, protocol = tcp/udp/tls/tls_passthrough + + if is_stream_route(route) then + -- returns a local variable instead of using a tail call + -- to avoid NYI + local str = expr_buf:get() + return str + end + + -- http expression, protocol = http/https/grpc/grpcs + + local gen = gen_for_field("http.method", OP_EQUAL, methods) + if gen then + expression_append(expr_buf, LOGICAL_AND, gen) + end + + if not is_empty_field(hosts) then + hosts_buf:reset():put("(") + + for i, h in ipairs(hosts) do + local host, port = split_host_port(h) + + local op = OP_EQUAL + if byte(host) == ASTERISK then + -- postfix matching + op = OP_POSTFIX + host = host:sub(2) + + elseif byte(host, -1) == ASTERISK then + -- prefix matching + op = OP_PREFIX + host = host:sub(1, -2) + end + + local exp = "http.host ".. op .. " r#\"" .. host .. "\"#" + if port then + exp = "(" .. exp .. LOGICAL_AND .. + "net.dst.port ".. OP_EQUAL .. " " .. port .. ")" + end + expression_append(hosts_buf, LOGICAL_OR, exp, i) + end -- for route.hosts + + expression_append(expr_buf, LOGICAL_AND, + hosts_buf:put(")"):get()) + end + + gen = gen_for_field("http.path", function(path) + return is_regex_magic(path) and OP_REGEX or OP_PREFIX + end, paths, function(op, p) + if op == OP_REGEX then + -- 1. strip leading `~` + -- 2. prefix with `^` to match the anchored behavior of the traditional router + -- 3. update named capture opening tag for rust regex::Regex compatibility + return "^" .. p:sub(2):gsub("?<", "?P<") + end + + return p + end) + if gen then + expression_append(expr_buf, LOGICAL_AND, gen) + end + + if not is_empty_field(headers) then + headers_buf:reset() + + for h, v in pairs(headers) do + single_header_buf:reset():put("(") + + for i, value in ipairs(v) do + local name = "any(lower(http.headers." .. replace_dashes_lower(h) .. "))" + local op = OP_EQUAL + + -- value starts with "~*" + if byte(value, 1) == TILDE and byte(value, 2) == ASTERISK then + value = value:sub(3) + op = OP_REGEX + end + + expression_append(single_header_buf, LOGICAL_OR, + name .. " " .. op .. " " .. escape_str(value:lower()), i) + end + + expression_append(headers_buf, LOGICAL_AND, + single_header_buf:put(")"):get()) + end + + expression_append(expr_buf, LOGICAL_AND, headers_buf:get()) + end + + local str = expr_buf:get() + + -- returns a local variable instead of using a tail call + -- to avoid NYI + return str +end + + +local lshift_uint64 +do + local ffi = require("ffi") + local ffi_uint = ffi.new("uint64_t") + + lshift_uint64 = function(v, offset) + ffi_uint = v + return lshift(ffi_uint, offset) + end +end + + +local stream_get_priority +do + -- compatible with http priority + local STREAM_SNI_BIT = lshift_uint64(0x01ULL, 61) + + -- IP > PORT > CIDR + local IP_BIT = lshift_uint64(0x01ULL, 3) + local PORT_BIT = lshift_uint64(0x01ULL, 2) + local CIDR_BIT = lshift_uint64(0x01ULL, 0) + + local function calc_ip_weight(ips) + local weight = 0x0ULL + + if is_empty_field(ips) then + return weight + end + + for i = 1, #ips do + local ip = ips[i].ip + local port = ips[i].port + + if not is_null(ip) then + if ip:find("/", 1, true) then + weight = bor(weight, CIDR_BIT) + + else + weight = bor(weight, IP_BIT) + end + end + + if not is_null(port) then + weight = bor(weight, PORT_BIT) + end + end + + return weight + end + + stream_get_priority = function(snis, srcs, dsts) + local match_weight = 0x0ULL + + -- [sni] has higher priority than [src] or [dst] + if not is_empty_field(snis) then + match_weight = STREAM_SNI_BIT + end + + -- [src] + [dst] has higher priority than [sni] + if not is_empty_field(srcs) and + not is_empty_field(dsts) + then + match_weight = STREAM_SNI_BIT + end + + local src_bits = calc_ip_weight(srcs) + local dst_bits = calc_ip_weight(dsts) + + local priority = bor(match_weight, + lshift_uint64(src_bits, 4), + dst_bits) + + return priority + end +end + + +local MAX_HEADER_COUNT = 255 + + +local PLAIN_HOST_ONLY_BIT = lshift_uint64(0x01ULL, 60) +local REGEX_URL_BIT = lshift_uint64(0x01ULL, 51) + + +-- convert a route to a priority value for use in the ATC router +-- priority must be a 64-bit non negative integer +-- format (big endian): +-- 0 1 2 3 +-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-- +-----+-+---------------+-+-------------------------------------+ +-- | W |P| Header |R| Regex | +-- | G |L| |G| Priority | +-- | T |N| Count |X| | +-- +-----+-+-----------------+-------------------------------------+ +-- | Regex Priority | Max Length | +-- | (cont) | | +-- | | | +-- +-------------------------+-------------------------------------+ +local function get_priority(route) + local snis = route.snis + local srcs = route.sources + local dsts = route.destinations + + -- stream expression + + if not is_empty_field(srcs) or + not is_empty_field(dsts) + then + return stream_get_priority(snis, srcs, dsts) + end + + -- http expression + + local methods = route.methods + local hosts = route.hosts + local paths = route.paths + local headers = route.headers + + local match_weight = 0 -- 0x0ULL + + if not is_empty_field(methods) then + match_weight = match_weight + 1 + end + + if not is_empty_field(hosts) then + match_weight = match_weight + 1 + end + + local headers_count = is_empty_field(headers) and 0 or tb_nkeys(headers) + + if headers_count > 0 then + match_weight = match_weight + 1 + + if headers_count > MAX_HEADER_COUNT then + ngx.log(ngx.WARN, "too many headers in route ", route.id, + " headers count capped at ", MAX_HEADER_COUNT, + " when sorting") + headers_count = MAX_HEADER_COUNT + end + end + + if not is_empty_field(snis) then + match_weight = match_weight + 1 + end + + local plain_host_only = type(hosts) == "table" + + if plain_host_only then + for _, h in ipairs(hosts) do + if h:find("*", nil, true) then + plain_host_only = false + break + end + end + end + + local uri_length = 0 + local regex_url = false + + if not is_empty_field(paths) then + match_weight = match_weight + 1 + + local p = paths[1] + + if is_regex_magic(p) then + regex_url = true + + else + uri_length = #p + end + + for i = 2, #paths do + p = paths[i] + + if regex_url then + assert(is_regex_magic(p), + "cannot mix regex and non-regex paths in get_priority()") + + else + assert(#p == uri_length, + "cannot mix different length prefixes in get_priority()") + end + end + end + + local match_weight = lshift_uint64(match_weight, 61) + local headers_count = lshift_uint64(headers_count, 52) + + local regex_priority = lshift_uint64(regex_url and route.regex_priority or 0, 19) + local max_length = band(uri_length, 0x7FFFF) + + local priority = bor(match_weight, + plain_host_only and PLAIN_HOST_ONLY_BIT or 0, + regex_url and REGEX_URL_BIT or 0, + headers_count, + regex_priority, + max_length) + + return priority +end + + +return { + OP_EQUAL = OP_EQUAL, + + LOGICAL_OR = LOGICAL_OR, + LOGICAL_AND = LOGICAL_AND, + + split_host_port = split_host_port, + + is_empty_field = is_empty_field, + gen_for_field = gen_for_field, + + get_expression = get_expression, + get_priority = get_priority, +} + diff --git a/kong/router/utils.lua b/kong/router/utils.lua index a70eb5077c9..5c3af208673 100644 --- a/kong/router/utils.lua +++ b/kong/router/utils.lua @@ -389,39 +389,6 @@ do end -local parse_ip_addr -do - local bit = require("bit") - local ipmatcher = require("resty.ipmatcher") - - local band, lshift, rshift = bit.band, bit.lshift, bit.rshift - - parse_ip_addr = function(ip) - local addr, mask = ipmatcher.split_ip(ip) - - if not mask then - return addr - end - - local ipv4 = ipmatcher.parse_ipv4(addr) - - -- FIXME: support ipv6 - if not ipv4 then - return addr, mask - end - - local cidr = lshift(rshift(ipv4, 32 - mask), 32 - mask) - - local n1 = band( cidr , 0xff) - local n2 = band(rshift(cidr, 8), 0xff) - local n3 = band(rshift(cidr, 16), 0xff) - local n4 = band(rshift(cidr, 24), 0xff) - - return n4 .. "." .. n3 .. "." .. n2 .. "." .. n1, mask - end -end - - return { DEFAULT_MATCH_LRUCACHE_SIZE = DEFAULT_MATCH_LRUCACHE_SIZE, @@ -435,6 +402,4 @@ return { route_match_stat = route_match_stat, is_regex_magic = is_regex_magic, phonehome_statistics = phonehome_statistics, - - parse_ip_addr = parse_ip_addr, } diff --git a/kong/runloop/handler.lua b/kong/runloop/handler.lua index e6cf91469f9..1a5f3a00a00 100644 --- a/kong/runloop/handler.lua +++ b/kong/runloop/handler.lua @@ -1112,7 +1112,7 @@ return { local has_timing = ctx.has_timing if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "before:router") + req_dyn_hook_run_hooks("timing", "before:router") end -- routing request @@ -1120,7 +1120,7 @@ return { local match_t = router:exec(ctx) if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "after:router") + req_dyn_hook_run_hooks("timing", "after:router") end if not match_t then @@ -1141,7 +1141,7 @@ return { ctx.workspace = match_t.route and match_t.route.ws_id if has_timing then - req_dyn_hook_run_hooks(ctx, "timing", "workspace_id:got", ctx.workspace) + req_dyn_hook_run_hooks("timing", "workspace_id:got", ctx.workspace) end local host = var.host diff --git a/kong/timing/init.lua b/kong/timing/init.lua index 9b9c5df3199..7f64d2e28bf 100644 --- a/kong/timing/init.lua +++ b/kong/timing/init.lua @@ -7,6 +7,8 @@ local ngx = ngx local ngx_var = ngx.var local ngx_req_set_header = ngx.req.set_header +local assert = assert +local ipairs = ipairs local string_format = string.format local request_id_get = require("kong.tracing.request_id").get @@ -62,7 +64,9 @@ function _M.auth() return end - assert(ngx.ctx.req_trace_id == nil) + local ngx_ctx = ngx.ctx + + assert(ngx_ctx.req_trace_id == nil) local http_x_kong_request_debug = ngx_var.http_x_kong_request_debug local http_x_kong_request_debug_token = ngx_var.http_x_kong_request_debug_token @@ -100,8 +104,8 @@ function _M.auth() loopback = loopback, }) ctx:set_context_prop("request_id", request_id_get()) - ngx.ctx.req_trace_ctx = ctx - req_dyn_hook.enable_on_this_request("timing") + ngx_ctx.req_trace_ctx = ctx + req_dyn_hook.enable_on_this_request("timing", ngx_ctx) end @@ -147,7 +151,8 @@ end function _M.header_filter() - local req_tr_ctx = ngx.ctx.req_trace_ctx + local ngx_ctx = ngx.ctx + local req_tr_ctx = ngx_ctx.req_trace_ctx req_tr_ctx:mock_upstream_phase() local output = req_tr_ctx:to_json() @@ -155,11 +160,11 @@ function _M.header_filter() if #output >= HEADER_JSON_TRUNCATE_LENGTH and not req_tr_ctx:from_loopback() then output = assert(cjson.encode({ truncated = true, - request_id = ngx.ctx.req_trace_ctx:get_root_context_kv("request_id"), + request_id = ngx_ctx.req_trace_ctx:get_root_context_kv("request_id"), message = "Output is truncated, please check the error_log for full output by filtering with the request_id.", })) - ngx.ctx.req_trace_ctx.log = true + ngx_ctx.req_trace_ctx.log = true end ngx.header["X-Kong-Request-Debug-Output"] = output diff --git a/spec/01-unit/08-router_spec.lua b/spec/01-unit/08-router_spec.lua index f209586c895..3078c907f82 100644 --- a/spec/01-unit/08-router_spec.lua +++ b/spec/01-unit/08-router_spec.lua @@ -4699,6 +4699,7 @@ for _, flavor in ipairs({ "traditional", "traditional_compatible", "expressions" service = service, route = { id = "e8fb37f1-102d-461e-9c51-6608a6bb8101", + protocols = { "tls" }, snis = { "www.example.org" }, sources = { { ip = "127.0.0.1" }, diff --git a/spec/fixtures/custom_plugins/kong/plugins/invalidations/handler.lua b/spec/fixtures/custom_plugins/kong/plugins/invalidations/handler.lua index 91ccfd67e5a..059a96b61c6 100644 --- a/spec/fixtures/custom_plugins/kong/plugins/invalidations/handler.lua +++ b/spec/fixtures/custom_plugins/kong/plugins/invalidations/handler.lua @@ -15,6 +15,10 @@ function Invalidations:init_worker() assert(kong.cluster_events:subscribe("invalidations", function(key) counts[key] = (counts[key] or 0) + 1 end)) + + assert(kong.cluster_events:subscribe("invalidations_kong_core_db_cache", function(key) + counts[key] = (counts[key] or 0) + 1 + end)) end