This is an automated email from the ASF dual-hosted git repository.
kichan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 915a7bc2d7 Add support for verified addr api for lua plugin (#12650)
915a7bc2d7 is described below
commit 915a7bc2d77deed80f28fffb5f126b4e85927824
Author: Kit Chan <[email protected]>
AuthorDate: Wed Nov 12 14:16:28 2025 -0800
Add support for verified addr api for lua plugin (#12650)
* Add support for verified addr for lua plugin
* Update tests/gold_tests/pluginTest/lua/verified_addr.lua
Co-authored-by: Copilot <[email protected]>
* Update tests/gold_tests/pluginTest/lua/verified_addr.lua
Co-authored-by: Copilot <[email protected]>
* Update doc/admin-guide/plugins/lua.en.rst
Co-authored-by: Copilot <[email protected]>
* Update doc/admin-guide/plugins/lua.en.rst
Co-authored-by: Copilot <[email protected]>
* Update doc/admin-guide/plugins/lua.en.rst
Co-authored-by: Copilot <[email protected]>
* Update plugins/lua/ts_lua_client_request.cc
Co-authored-by: Copilot <[email protected]>
* Update plugins/lua/ts_lua_client_request.cc
Co-authored-by: Copilot <[email protected]>
* fix format
* Format fix
* Fix Format
---------
Co-authored-by: Copilot <[email protected]>
---
doc/admin-guide/plugins/lua.en.rst | 84 ++++++++++++++++++++
plugins/lua/ts_lua_client_request.cc | 89 ++++++++++++++++++++++
.../pluginTest/lua/lua_verified_addr.test.py | 79 +++++++++++++++++++
tests/gold_tests/pluginTest/lua/verified_addr.lua | 87 +++++++++++++++++++++
4 files changed, 339 insertions(+)
diff --git a/doc/admin-guide/plugins/lua.en.rst
b/doc/admin-guide/plugins/lua.en.rst
index dae206f1de..c6c0c3b782 100644
--- a/doc/admin-guide/plugins/lua.en.rst
+++ b/doc/admin-guide/plugins/lua.en.rst
@@ -1053,6 +1053,90 @@ Here is an example:
:ref:`TOP <admin-plugins-ts-lua>`
+ts.client_request.client_addr.get_verified_addr
+-----------------------------------------------
+**syntax:** *ip, family = ts.client_request.client_addr.get_verified_addr()*
+
+**context:** do_remap/do_os_response or do_global_* or later
+
+**description**: This function can be used to get the verified client IP
address for the current transaction.
+
+The verified address is set by plugins (typically earlier in the transaction)
to provide a reliable client IP address.
+This is useful when Traffic Server is behind a proxy or load balancer that
provides the real client IP through
+mechanisms like PROXY protocol, X-Forwarded-For headers, or X-Real-IP headers.
+
+The ts.client_request.client_addr.get_verified_addr function returns two
values: ip is a string and family is a number.
+If no verified address has been set, both return values will be nil.
+
+Here is an example:
+
+::
+
+ function do_remap()
+ ip, family = ts.client_request.client_addr.get_verified_addr()
+ if ip then
+ ts.debug(ip) -- 192.168.1.100
+ ts.debug(family) -- 2(AF_INET)
+ else
+ ts.debug("No verified address set")
+ end
+ return 0
+ end
+
+When ``proxy.config.acl.subjects`` is set to ``PLUGIN``, Traffic Server will
use the verified address (if set)
+for ACL evaluation instead of the actual client connection address.
+
+:ref:`TOP <admin-plugins-ts-lua>`
+
+ts.client_request.client_addr.set_verified_addr
+-----------------------------------------------
+**syntax:** *ts.client_request.client_addr.set_verified_addr(ip, family)*
+
+**context:** do_remap/do_os_response or do_global_* or later
+
+**description**: This function can be used to set a verified client IP address
for the current transaction.
+
+This function enables plugins to provide a reliable client IP address for
Traffic Server and other plugins.
+Plugins that call this function are expected to validate the IP address before
setting it.
+
+**Parameters:**
+
+* ``ip`` - string: The IP address to set (e.g., "192.168.1.100" or
"2001:db8::1")
+* ``family`` - number: The address family (`TS_LUA_AF_INET` for IPv4,
`TS_LUA_AF_INET6` for IPv6)
+
+Here is an example:
+
+::
+
+ function do_remap()
+ -- Get real client IP from X-Forwarded-For header
+ local xff = ts.client_request.header["X-Forwarded-For"]
+
+ if xff then
+ -- Parse the first IP from X-Forwarded-For
+ local real_ip = string.match(xff, "([^,]+)")
+
+ if real_ip then
+ -- Trim whitespace
+ real_ip = real_ip:match("^%s*(.-)%s*$")
+
+ -- Set as verified address (IPv4 example,
family=TS_LUA_AF_INET)
+ ts.client_request.client_addr.set_verified_addr(real_ip,
TS_LUA_AF_INET)
+ ts.debug("Set verified address to: " .. real_ip)
+ end
+ end
+
+ return 0
+ end
+
+**Important Notes:**
+
+* For IPv6 addresses, use TS_LUA_AF_INET6.
+* Set the verified address as early as possible in the transaction lifecycle
to ensure it's available
+ for all subsequent processing.
+
+:ref:`TOP <admin-plugins-ts-lua>`
+
ts.client_request.get_url_host
------------------------------
**syntax:** *host = ts.client_request.get_url_host()*
diff --git a/plugins/lua/ts_lua_client_request.cc
b/plugins/lua/ts_lua_client_request.cc
index 0fc9e534ba..935458885b 100644
--- a/plugins/lua/ts_lua_client_request.cc
+++ b/plugins/lua/ts_lua_client_request.cc
@@ -66,6 +66,8 @@ static int ts_lua_client_request_client_addr_get_ip(lua_State
*L);
static int ts_lua_client_request_client_addr_get_port(lua_State *L);
static int ts_lua_client_request_client_addr_get_addr(lua_State *L);
static int ts_lua_client_request_client_addr_get_incoming_port(lua_State *L);
+static int ts_lua_client_request_client_addr_get_verified_addr(lua_State *L);
+static int ts_lua_client_request_client_addr_set_verified_addr(lua_State *L);
static void ts_lua_inject_client_request_ssl_reused_api(lua_State *L);
static int ts_lua_client_request_get_ssl_reused(lua_State *L);
@@ -124,6 +126,12 @@ ts_lua_inject_client_request_client_addr_api(lua_State *L)
lua_pushcfunction(L, ts_lua_client_request_client_addr_get_incoming_port);
lua_setfield(L, -2, "get_incoming_port");
+ lua_pushcfunction(L, ts_lua_client_request_client_addr_get_verified_addr);
+ lua_setfield(L, -2, "get_verified_addr");
+
+ lua_pushcfunction(L, ts_lua_client_request_client_addr_set_verified_addr);
+ lua_setfield(L, -2, "set_verified_addr");
+
lua_setfield(L, -2, "client_addr");
}
@@ -1139,3 +1147,84 @@ ts_lua_client_request_get_ssl_curve(lua_State *L)
return 1;
}
+
+static int
+ts_lua_client_request_client_addr_get_verified_addr(lua_State *L)
+{
+ struct sockaddr const *verified_addr;
+ ts_lua_http_ctx *http_ctx;
+ int family = AF_UNSPEC;
+ char vip[128] = "";
+
+ GET_HTTP_CONTEXT(http_ctx, L);
+
+ if (TSHttpTxnVerifiedAddrGet(http_ctx->txnp, &verified_addr) == TS_SUCCESS) {
+ if (verified_addr->sa_family == AF_INET) {
+ inet_ntop(AF_INET, (const void *)&((struct sockaddr_in
*)verified_addr)->sin_addr, vip, sizeof(vip));
+ family = AF_INET;
+ lua_pushstring(L, vip);
+ lua_pushnumber(L, family);
+ } else if (verified_addr->sa_family == AF_INET6) {
+ inet_ntop(AF_INET6, (const void *)&((struct sockaddr_in6
*)verified_addr)->sin6_addr, vip, sizeof(vip));
+ family = AF_INET6;
+ lua_pushstring(L, vip);
+ lua_pushnumber(L, family);
+ } else {
+ lua_pushnil(L);
+ lua_pushnil(L);
+ }
+ } else {
+ lua_pushnil(L);
+ lua_pushnil(L);
+ }
+
+ return 2;
+}
+
+static int
+ts_lua_client_request_client_addr_set_verified_addr(lua_State *L)
+{
+ union {
+ struct sockaddr_in sin4;
+ struct sockaddr_in6 sin6;
+ struct sockaddr sa;
+ } addr;
+ memset(&addr, 0, sizeof(addr));
+ ts_lua_http_ctx *http_ctx;
+ int n;
+ int family;
+ const char *vip;
+ size_t vip_len;
+
+ GET_HTTP_CONTEXT(http_ctx, L);
+
+ n = lua_gettop(L);
+
+ if (n == 2) {
+ vip = luaL_checklstring(L, 1, &vip_len);
+ family = luaL_checknumber(L, 2);
+
+ if (family == AF_INET) {
+ addr.sin4.sin_family = AF_INET;
+ addr.sin4.sin_port = 0;
+ if (!inet_pton(family, vip, &addr.sin4.sin_addr)) {
+ return luaL_error(L, "invalid ipv4 address");
+ }
+ } else if (family == AF_INET6) {
+ addr.sin6.sin6_family = AF_INET6;
+ addr.sin6.sin6_port = 0;
+ if (!inet_pton(family, vip, &addr.sin6.sin6_addr)) {
+ return luaL_error(L, "invalid ipv6 address");
+ }
+ } else {
+ return luaL_error(L, "invalid address family");
+ }
+
+ TSHttpTxnVerifiedAddrSet(http_ctx->txnp, &addr.sa);
+ } else {
+ return luaL_error(L, "incorrect # of arguments to
ts.client_request.client_addr.set_verified_addr, receiving %d instead of 2",
+ n);
+ }
+
+ return 0;
+}
diff --git a/tests/gold_tests/pluginTest/lua/lua_verified_addr.test.py
b/tests/gold_tests/pluginTest/lua/lua_verified_addr.test.py
new file mode 100644
index 0000000000..adfd0f6656
--- /dev/null
+++ b/tests/gold_tests/pluginTest/lua/lua_verified_addr.test.py
@@ -0,0 +1,79 @@
+'''
+Test lua verified address functionality
+'''
+# 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.
+
+Test.Summary = '''
+Test lua verified address get/set functionality
+'''
+
+Test.SkipUnless(Condition.PluginExists('tslua.so'),)
+
+Test.ContinueOnFail = True
+
+# Define ATS process
+ts = Test.MakeATSProcess("ts")
+
+# Configure remap
+ts.Disk.remap_config.AddLine(f"map / http://127.0.0.1 @plugin=tslua.so
@pparam=verified_addr.lua")
+
+# Copy the Lua script
+ts.Setup.Copy("verified_addr.lua", ts.Variables.CONFIGDIR)
+
+# Enable debug logging
+ts.Disk.records_config.update({'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'ts_lua'})
+
+# Test 1: IPv4 verified address
+tr = Test.AddTestRun("Lua Verified Address - IPv4")
+ps = tr.Processes.Default
+ps.StartBefore(Test.Processes.ts)
+tr.MakeCurlCommand(f"-s -H 'X-Real-IP: 192.0.2.100'
http://127.0.0.1:{ts.Variables.port}", ts=ts)
+ps.Env = ts.Env
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression(
+ "initial:nil;set:success;get:192.0.2.100:2;", "IPv4 verified address
should be set and retrieved correctly")
+tr.StillRunningAfter = ts
+
+# Test 2: IPv6 verified address
+tr = Test.AddTestRun("Lua Verified Address - IPv6")
+ps = tr.Processes.Default
+tr.MakeCurlCommand(f"-s -H 'X-Real-IP-V6: 2001:db8::1'
http://127.0.0.1:{ts.Variables.port}", ts=ts)
+ps.Env = ts.Env
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression(
+ "initial:nil;setv6:success;getv6:2001:db8::1:10;", "IPv6 verified address
should be set and retrieved correctly")
+tr.StillRunningAfter = ts
+
+# Test 3: Invalid IP address (should be rejected)
+tr = Test.AddTestRun("Lua Verified Address - Invalid IP")
+ps = tr.Processes.Default
+tr.MakeCurlCommand(f"-s -H 'X-Invalid-IP: not.a.valid.ip'
http://127.0.0.1:{ts.Variables.port}", ts=ts)
+ps.Env = ts.Env
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression("invalid:rejected;",
"Invalid IP address should be rejected")
+tr.StillRunningAfter = ts
+
+# Test 4: Both IPv4 and IPv6 in sequence
+tr = Test.AddTestRun("Lua Verified Address - IPv4 then IPv6")
+ps = tr.Processes.Default
+tr.MakeCurlCommand(f"-s -H 'X-Real-IP: 203.0.113.42' -H 'X-Real-IP-V6:
2001:db8::42' http://127.0.0.1:{ts.Variables.port}", ts=ts)
+ps.Env = ts.Env
+ps.ReturnCode = 0
+ps.Streams.stdout.Content = Testers.ContainsExpression(
+
"initial:nil;set:success;get:203.0.113.42:2;setv6:success;getv6:2001:db8::42:10;",
+ "Both IPv4 and IPv6 verified addresses should work in sequence")
+tr.StillRunningAfter = ts
diff --git a/tests/gold_tests/pluginTest/lua/verified_addr.lua
b/tests/gold_tests/pluginTest/lua/verified_addr.lua
new file mode 100644
index 0000000000..6ee3ee8e85
--- /dev/null
+++ b/tests/gold_tests/pluginTest/lua/verified_addr.lua
@@ -0,0 +1,87 @@
+-- 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.
+
+function do_remap()
+ local result = ""
+
+ -- Test 1: Check if verified address is initially nil
+ local ip1, family1 = ts.client_request.client_addr.get_verified_addr()
+ if not ip1 then
+ result = result .. "initial:nil;"
+ end
+
+ -- Test 2: Set an IPv4 verified address from X-Real-IP header
+ local real_ip = ts.client_request.header["X-Real-IP"]
+ if real_ip then
+ local success, err = pcall(function()
+ ts.client_request.client_addr.set_verified_addr(real_ip,
TS_LUA_AF_INET)
+ end)
+
+ if success then
+ result = result .. "set:success;"
+
+ -- Test 3: Get the verified address we just set
+ local ip2, family2 =
ts.client_request.client_addr.get_verified_addr()
+ if ip2 then
+ result = result .. "get:" .. ip2 .. ":" .. tostring(family2)
.. ";"
+ else
+ result = result .. "get:failed;"
+ end
+ else
+ result = result .. "set:failed;"
+ end
+ end
+
+ -- Test 4: Try setting an IPv6 address from X-Real-IP-V6 header
+ local real_ipv6 = ts.client_request.header["X-Real-IP-V6"]
+ if real_ipv6 then
+ local success, err = pcall(function()
+ ts.client_request.client_addr.set_verified_addr(real_ipv6,
TS_LUA_AF_INET6)
+ end)
+
+ if success then
+ result = result .. "setv6:success;"
+
+ -- Get the IPv6 verified address
+ local ip3, family3 =
ts.client_request.client_addr.get_verified_addr()
+ if ip3 then
+ result = result .. "getv6:" .. ip3 .. ":" .. tostring(family3)
.. ";"
+ else
+ result = result .. "getv6:failed;"
+ end
+ else
+ result = result .. "setv6:failed;"
+ end
+ end
+
+ -- Test 5: Try setting an invalid address (should fail)
+ local invalid_ip = ts.client_request.header["X-Invalid-IP"]
+ if invalid_ip then
+ local success, err = pcall(function()
+ ts.client_request.client_addr.set_verified_addr(invalid_ip,
TS_LUA_AF_INET)
+ end)
+
+ if not success then
+ result = result .. "invalid:rejected;"
+ else
+ result = result .. "invalid:accepted;"
+ end
+ end
+
+ -- Return the result in the response
+ ts.http.set_resp(200, result)
+ return 0
+end