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

Reply via email to