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

maskit 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 891c4621d0 TS API: Add TSHttpTxnVerifiedAddrSet/Get and real-ip plugin 
(#12359)
891c4621d0 is described below

commit 891c4621d0935d3f1a5ee3bff887a838d162a60a
Author: Masakazu Kitajo <[email protected]>
AuthorDate: Mon Sep 15 17:18:57 2025 -0600

    TS API: Add TSHttpTxnVerifiedAddrSet/Get and real-ip plugin (#12359)
    
    The motivation is to enable plugins to provide client's "real" IP address 
for ATS core and other plugins. Plugins that use the setter function are 
expected to read clien's real IP address from somewhere (e.g. Forwarded header 
field), and also check the validity. If a plugin successfully verifies client's 
IP address, it may call the setter function for later use. The IP address set 
by a plugin will be used by ATS remap ACL, and other plugins which call the 
getter function, if they are  [...]
---
 cmake/ExperimentalPlugins.cmake                    |  1 +
 doc/admin-guide/files/records.yaml.en.rst          |  1 +
 doc/admin-guide/plugins/index.en.rst               |  4 +
 doc/admin-guide/plugins/realip.en.rst              | 86 ++++++++++++++++++++++
 .../api/functions/TSHttpTxnVerifiedAddrSet.en.rst  | 69 +++++++++++++++++
 include/proxy/IPAllow.h                            |  2 +-
 include/proxy/ProxyTransaction.h                   | 20 +++++
 include/ts/ts.h                                    |  4 +
 plugins/experimental/CMakeLists.txt                |  3 +
 .../experimental/realip}/CMakeLists.txt            | 12 ++-
 plugins/experimental/realip/address_setter.cc      | 43 +++++++++++
 plugins/experimental/realip/address_setter.h       | 36 +++++++++
 plugins/experimental/realip/address_source.cc      | 72 ++++++++++++++++++
 plugins/experimental/realip/address_source.h       | 41 +++++++++++
 plugins/experimental/realip/pp.cc                  | 52 +++++++++++++
 plugins/experimental/realip/pp.h                   | 34 +++++++++
 plugins/experimental/realip/realip.cc              | 67 +++++++++++++++++
 plugins/experimental/realip/realip.h               | 23 ++++++
 plugins/experimental/realip/simple.cc              | 76 +++++++++++++++++++
 plugins/experimental/realip/simple.h               | 34 +++++++++
 plugins/header_rewrite/conditions.cc               |  3 +
 plugins/header_rewrite/header_rewrite.cc           |  2 +
 plugins/header_rewrite/operators.cc                |  2 +
 plugins/header_rewrite/statement.h                 |  6 +-
 src/api/InkAPI.cc                                  | 31 ++++++++
 src/proxy/IPAllow.cc                               |  3 +
 src/proxy/http/remap/UrlRewrite.cc                 |  4 +
 tests/gold_tests/pluginTest/tsapi/CMakeLists.txt   |  1 +
 .../pluginTest/tsapi/hrw_verified_addr.conf        | 19 +++++
 .../pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.cc | 81 ++++++++++++++++++++
 .../tsapi/test_TSHttpTxnVerifiedAddr.test.py       | 70 ++++++++++++++++++
 31 files changed, 894 insertions(+), 8 deletions(-)

diff --git a/cmake/ExperimentalPlugins.cmake b/cmake/ExperimentalPlugins.cmake
index 2fa1524c77..e8ea614134 100644
--- a/cmake/ExperimentalPlugins.cmake
+++ b/cmake/ExperimentalPlugins.cmake
@@ -87,6 +87,7 @@ auto_option(
   ${_DEFAULT}
 )
 auto_option(RATE_LIMIT FEATURE_VAR BUILD_RATE_LIMIT DEFAULT ${_DEFAULT})
+auto_option(REALIP FEATURE_VAR BUILD_REALIP DEFAULT ${_DEFAULT})
 auto_option(REDO_CACHE_LOOKUP FEATURE_VAR BUILD_REDO_CACHE_LOOKUP DEFAULT 
${_DEFAULT})
 auto_option(SSLHEADERS FEATURE_VAR BUILD_SSLHEADERS DEFAULT ${_DEFAULT})
 auto_option(STALE_RESPONSE FEATURE_VAR BUILD_STALE_RESPONSE DEFAULT 
${_DEFAULT})
diff --git a/doc/admin-guide/files/records.yaml.en.rst 
b/doc/admin-guide/files/records.yaml.en.rst
index 706a4b55e8..920ec5d7d4 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -2186,6 +2186,7 @@ IP Allow
    ============= 
======================================================================
    ``PEER``      Use the IP address of the peer
    ``PROXY``     Use the IP address from PROXY protocol
+   ``PLUGIN``    Use the IP address verified by a plugin
    ============= 
======================================================================
 
 
diff --git a/doc/admin-guide/plugins/index.en.rst 
b/doc/admin-guide/plugins/index.en.rst
index 908c61b649..d92a4b1da0 100644
--- a/doc/admin-guide/plugins/index.en.rst
+++ b/doc/admin-guide/plugins/index.en.rst
@@ -185,6 +185,7 @@ directory of the |TS| source tree. Experimental plugins can 
be compiled by passi
    Multiplexer <multiplexer.en>
    OpenTelemetry Tracer <otel_tracer.en>
    Rate Limit <rate_limit.en>
+   Real IP <realip.en>
    URI Signing <uri_signing.en>
    Legacy Signed URLs <url_sig.en>
    Slice <slice.en>
@@ -250,6 +251,9 @@ directory of the |TS| source tree. Experimental plugins can 
be compiled by passi
 :doc:`Rate Limit <rate_limit.en>`
    Simple transaction rate limiting.
 
+:doc:`Real IP <realip.en>`
+   Provides real client's IP address.
+
 :doc:`Remap Purge <remap_purge.en>`
    This remap plugin allows the administrator to easily setup remotely
    controlled ``PURGE`` for the content of an entire remap rule.
diff --git a/doc/admin-guide/plugins/realip.en.rst 
b/doc/admin-guide/plugins/realip.en.rst
new file mode 100644
index 0000000000..7846cc4b29
--- /dev/null
+++ b/doc/admin-guide/plugins/realip.en.rst
@@ -0,0 +1,86 @@
+.. 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.
+
+
+   .. include:: ../../common.defs
+
+.. _admin-plugins-realip:
+
+Real IP Plugin
+*******************
+
+Description
+===========
+The `realip` plugin reads an IP address from a specified data source such as 
HTTP header field, and makes it available for ATS and
+its plugins as the verified client address.
+
+To use the verified client IP address for ACL, 
:ts:cv:`proxy.config.acl.subjects` needs to be set to `PLUGIN`.
+Similary, if you want to use the IP address on :doc:`header_rewrite 
plugin<header_rewrite.en>`, its `INBOUND_IP_SOURCE` needs to be set to `PLUGIN`.
+
+
+Configuration
+=============
+
+To enable the `realip` plugin, insert the following line in 
:file:`plugin.config`::
+
+    realip.so <config.yaml>
+
+The plugin supports 2 modes:
+
+simple
+------
+
+The plugin reads an IP address from a specified HTTP header field. These 
settings below are available for this mode:
+
+
++------------------+-------------------------------------------------------------------------------------------------------+
+| Setting name     | Description                                               
                                            |
++==================+=======================================================================================================+
+| `header`         | HTTP field name that has an IP address. The field value 
must be a valid IP address.                   |
+|                  | You cannot use this mode for Forwarded header field () 
because its field value is structured.         |
++------------------+-------------------------------------------------------------------------------------------------------+
+| `trustedAddress` | A list of trusted IP addresss, and/or IP address ranges.  
                                            |
+|                  | The IP address in the specified header field will not be 
used if the sender's address is not trusted. |
++------------------+-------------------------------------------------------------------------------------------------------+
+
+proxyProtocol
+-------------
+
+The plugins reads an IP address from PROXY protocol header field.
+Although there is no plugin settings for this mode, `server_ports` and 
`proxy_protocol_allowlist` on records.yaml need to be
+configured to accept PROXY protocol.
+
+
+Example Configuration
+=====================
+
+Use Client-IP header, if the header is sent from `192.168.0.0/24` or 
`127.0.0.1`.
+
+   .. code-block:: yaml
+
+      simple:
+        header: "Client-IP"
+        trustedAddress:
+          - "192.168.0.0/24"
+          - "127.0.0.1"
+
+
+Use PROXY protocol header.
+
+   .. code-block:: yaml
+
+      proxyProtocol:
diff --git a/doc/developer-guide/api/functions/TSHttpTxnVerifiedAddrSet.en.rst 
b/doc/developer-guide/api/functions/TSHttpTxnVerifiedAddrSet.en.rst
new file mode 100644
index 0000000000..5dce200415
--- /dev/null
+++ b/doc/developer-guide/api/functions/TSHttpTxnVerifiedAddrSet.en.rst
@@ -0,0 +1,69 @@
+.. 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.
+
+.. include:: ../../../common.defs
+
+.. default-domain:: cpp
+
+TSHttpTxnVerifiedAddrSet
+************************
+
+Sets a client IP address verified by a plugin.
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+    #include <ts/ts.h>
+
+.. function:: TSReturnCode TSHttpTxnVerifiedAddrSet(TSHttpTxn txnp, const 
struct sockaddr *addr)
+
+Description
+===========
+
+This function enables plugins to provide a reliable client IP address for |TS| 
and other plugins.
+This is useful if there is a proxy in front of |TS| and it forwards client's 
IP address by HTTP header field, PROXY protocol, etc.
+Plugins that call this function are expected to check the validity of the IP 
address.
+
+The provided address will be used if :ts:cv:`proxy.config.acl.subjects` is set 
to `PLUGIN`.
+Plugins can get the provided address by calling the getter function below if 
those need client's real IP address.
+
+The address `addr` is internally copied and |TS| core maintains its own copy.
+Plugins that call this function do not need to keep the original for later use.
+
+
+TSHttpTxnVerifiedAddrGet
+************************
+
+Gets the client IP address verified by a plugin.
+
+Synopsis
+========
+
+.. code-block:: cpp
+
+    #include <ts/ts.h>
+
+.. function:: TSReturnCode TSHttpTxnVerifiedAddrGet(TSHttpTxn txnp, const 
struct sockaddr **addr)
+
+Description
+===========
+
+This is the getter version of the above setter. This returns 1 if a verified 
address is available.
+Please note that a port number is not always available even if the function 
returns 1.
+
+The address returned is maintained by |TS| core. Plugins cannot free the 
memory.
diff --git a/include/proxy/IPAllow.h b/include/proxy/IPAllow.h
index 02027131df..9d707247a0 100644
--- a/include/proxy/IPAllow.h
+++ b/include/proxy/IPAllow.h
@@ -147,7 +147,7 @@ public:
 
   static constexpr const char *MODULE_NAME = "IPAllow";
 
-  enum Subject { PEER, PROXY, MAX_SUBJECTS };
+  enum Subject { PEER, PROXY, PLUGIN, MAX_SUBJECTS };
 
   /** An access control record and support data.
    * The primary point of this is to hold the backing configuration in memory 
while the ACL
diff --git a/include/proxy/ProxyTransaction.h b/include/proxy/ProxyTransaction.h
index b7be3d3ae3..8238b1c5c9 100644
--- a/include/proxy/ProxyTransaction.h
+++ b/include/proxy/ProxyTransaction.h
@@ -128,6 +128,9 @@ public:
   PoolableSession    *get_server_session() const;
   HttpSM             *get_sm() const;
 
+  void            set_verified_client_addr(const sockaddr *addr);
+  sockaddr const *get_verified_client_addr() const;
+
   // This function must return a non-negative number that is different for two 
in-progress transactions with the same proxy_ssn
   // session.
   //
@@ -150,6 +153,7 @@ protected:
   IOBufferReader *_reader    = nullptr;
 
 private:
+  struct sockaddr_storage _verified_addr = {};
 };
 
 ////////////////////////////////////////////////////////////
@@ -325,3 +329,19 @@ ProxyTransaction::get_remote_addr() const
     return nullptr;
   }
 }
+
+inline struct sockaddr const *
+ProxyTransaction::get_verified_client_addr() const
+{
+  return reinterpret_cast<const struct sockaddr *>(&_verified_addr);
+}
+
+inline void
+ProxyTransaction::set_verified_client_addr(const sockaddr *addr)
+{
+  if (addr->sa_family == AF_INET) {
+    memcpy(&_verified_addr, addr, sizeof(struct sockaddr_in));
+  } else {
+    memcpy(&_verified_addr, addr, sizeof(struct sockaddr_in6));
+  }
+}
diff --git a/include/ts/ts.h b/include/ts/ts.h
index 34f77829a3..3bd473fe11 100644
--- a/include/ts/ts.h
+++ b/include/ts/ts.h
@@ -2994,6 +2994,10 @@ TSReturnCode TSIpStringToAddr(const char *str, size_t 
str_len, struct sockaddr *
  */
 TSTxnType TSHttpTxnTypeGet(TSHttpTxn txnp);
 
+TSReturnCode TSHttpTxnVerifiedAddrSet(TSHttpTxn txnp, const struct sockaddr 
*addr);
+
+TSReturnCode TSHttpTxnVerifiedAddrGet(TSHttpTxn txnp, const struct sockaddr 
**addr);
+
 /* Get Arbitrary Txn info such as cache lookup details etc as defined in 
TSHttpTxnInfoKey */
 /**
    Return the particular txn info requested.
diff --git a/plugins/experimental/CMakeLists.txt 
b/plugins/experimental/CMakeLists.txt
index 38b01049b5..20a54f4705 100644
--- a/plugins/experimental/CMakeLists.txt
+++ b/plugins/experimental/CMakeLists.txt
@@ -86,6 +86,9 @@ endif()
 if(BUILD_RATE_LIMIT)
   add_subdirectory(rate_limit)
 endif()
+if(BUILD_REALIP)
+  add_subdirectory(realip)
+endif()
 if(BUILD_REDO_CACHE_LOOKUP)
   add_subdirectory(redo_cache_lookup)
 endif()
diff --git a/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt 
b/plugins/experimental/realip/CMakeLists.txt
similarity index 74%
copy from tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
copy to plugins/experimental/realip/CMakeLists.txt
index d5e3a088fa..38168979d4 100644
--- a/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
+++ b/plugins/experimental/realip/CMakeLists.txt
@@ -15,7 +15,11 @@
 #
 #######################
 
-add_autest_plugin(test_tsapi test_tsapi.cc)
-add_autest_plugin(test_TSHttpTxnServerAddrSet test_TSHttpTxnServerAddrSet.cc)
-add_autest_plugin(test_TSHttpSsnInfo test_TSHttpSsnInfo.cc)
-add_autest_plugin(test_TSVConnPPInfo test_TSVConnPPInfo.cc)
+project(rate_limit)
+
+add_atsplugin(realip realip.cc address_setter.cc address_source.cc simple.cc 
pp.cc)
+
+target_link_libraries(realip PRIVATE libswoc::libswoc yaml-cpp::yaml-cpp)
+verify_global_plugin(realip)
+# Only works as a global plugin at this time
+#verify_remap_plugin(realip)
diff --git a/plugins/experimental/realip/address_setter.cc 
b/plugins/experimental/realip/address_setter.cc
new file mode 100644
index 0000000000..2df81beb71
--- /dev/null
+++ b/plugins/experimental/realip/address_setter.cc
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+#include "realip.h"
+#include "address_setter.h"
+
+AddressSource *AddressSetter::source = nullptr;
+
+int
+AddressSetter::event_handler(TSCont /* contp ATS_UNUSED */, TSEvent event, 
void *edata)
+{
+  TSReleaseAssert(event == TS_EVENT_HTTP_READ_REQUEST_HDR);
+  TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+
+  if (source->verify(txnp)) {
+    struct sockaddr_storage addr_storage;
+    if (struct sockaddr *addr = source->get_address(txnp, &addr_storage); addr 
!= nullptr) {
+      TSHttpTxnVerifiedAddrSet(txnp, addr);
+    } else {
+      Dbg(dbg_ctl, "Failed to get client's IP address");
+    }
+  } else {
+    Dbg(dbg_ctl, "Failed to verify the IP address source");
+  }
+
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+
+  return TS_EVENT_NONE;
+}
diff --git a/plugins/experimental/realip/address_setter.h 
b/plugins/experimental/realip/address_setter.h
new file mode 100644
index 0000000000..853da32728
--- /dev/null
+++ b/plugins/experimental/realip/address_setter.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include "ts/ts.h"
+
+#include "address_source.h"
+
+class AddressSetter
+{
+public:
+  static int event_handler(TSCont contp, TSEvent event, void *edata);
+  static void
+  set_source(AddressSource *src)
+  {
+    AddressSetter::source = src;
+  }
+
+private:
+  static AddressSource *source;
+};
diff --git a/plugins/experimental/realip/address_source.cc 
b/plugins/experimental/realip/address_source.cc
new file mode 100644
index 0000000000..44b8a93082
--- /dev/null
+++ b/plugins/experimental/realip/address_source.cc
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+#include <arpa/inet.h>
+
+#include "realip.h"
+#include "address_source.h"
+#include "simple.h"
+#include "pp.h"
+
+AddressSource *
+AddressSourceBuilder::build(YAML::Node config)
+{
+  AddressSource *source = nullptr;
+  std::string    source_name;
+
+  for (YAML::iterator it = config.begin(); it != config.end(); ++it) {
+    if (source != nullptr) {
+      delete source;
+      source = nullptr;
+      TSError("[%s] Multiple sources are configured.", PLUGIN_NAME);
+      break;
+    }
+
+    source_name = it->first.as<std::string>();
+    if (source_name == "simple") {
+      source = new SimpleAddressSource(it->second);
+    } else if (source_name == "proxyProtocol") {
+      source = new ProxyProtocolAddressSource(it->second);
+    } else {
+      Dbg(dbg_ctl, "Unsupported source: %s", source_name.c_str());
+    }
+  }
+
+  if (source != nullptr) {
+    Dbg(dbg_ctl, "Address source \"%s\" was configured", source_name.c_str());
+  }
+
+  return source;
+}
+
+int
+AddressSource::inet_pton46(std::string str, struct sockaddr_storage *addr)
+{
+  int ret = 0;
+  if (str.find(':') != std::string::npos) {
+    struct sockaddr_in6 *addr6 = reinterpret_cast<struct sockaddr_in6 *>(addr);
+    addr6->sin6_family         = AF_INET6;
+    addr6->sin6_port           = 0;
+    ret                        = inet_pton(AF_INET6, str.c_str(), 
&(addr6->sin6_addr));
+  } else {
+    struct sockaddr_in *addr4 = reinterpret_cast<struct sockaddr_in *>(addr);
+    addr4->sin_family         = AF_INET;
+    addr4->sin_port           = 0;
+    ret                       = inet_pton(AF_INET, str.c_str(), 
&(addr4->sin_addr));
+  }
+  return ret;
+}
diff --git a/plugins/experimental/realip/address_source.h 
b/plugins/experimental/realip/address_source.h
new file mode 100644
index 0000000000..cf6699ced1
--- /dev/null
+++ b/plugins/experimental/realip/address_source.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <string_view>
+#include <yaml-cpp/yaml.h>
+
+#include "ts/ts.h"
+
+class AddressSource
+{
+public:
+  AddressSource()                                                              
       = default;
+  virtual ~AddressSource()                                                     
       = default;
+  virtual bool             verify(TSHttpTxn txnp)                              
       = 0;
+  virtual struct sockaddr *get_address(TSHttpTxn txnp, struct sockaddr_storage 
*addr) = 0;
+
+protected:
+  int inet_pton46(std::string str, struct sockaddr_storage *addr);
+};
+
+class AddressSourceBuilder
+{
+public:
+  static AddressSource *build(YAML::Node config);
+};
diff --git a/plugins/experimental/realip/pp.cc 
b/plugins/experimental/realip/pp.cc
new file mode 100644
index 0000000000..269c2c0dd3
--- /dev/null
+++ b/plugins/experimental/realip/pp.cc
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+#include "realip.h"
+#include "pp.h"
+
+ProxyProtocolAddressSource::ProxyProtocolAddressSource(YAML::Node /* config 
ATS_UNUSED */)
+{
+  // There is no settings for this address source.
+}
+
+bool
+ProxyProtocolAddressSource::verify(TSHttpTxn /* txnp ATS_UNUSED */)
+{
+  // This address source expects that 
proxy.config.http.proxy_protocol_allowlist is configured appropriately.
+  return true;
+}
+
+struct sockaddr *
+ProxyProtocolAddressSource::get_address(TSHttpTxn txnp, struct 
sockaddr_storage *addr)
+{
+  struct sockaddr       *ret = nullptr;
+  const struct sockaddr *pp_addr;
+  int                    pp_addr_len;
+
+  TSVConn vconn = TSHttpSsnClientVConnGet(TSHttpTxnSsnGet(txnp));
+  if (TSVConnPPInfoGet(vconn, TS_PP_INFO_SRC_ADDR, reinterpret_cast<const char 
**>(&pp_addr), &pp_addr_len) == TS_SUCCESS) {
+    if (pp_addr->sa_family == AF_INET) {
+      memcpy(addr, pp_addr, sizeof(struct sockaddr_in));
+      ret = reinterpret_cast<struct sockaddr *>(addr);
+    } else {
+      memcpy(addr, pp_addr, sizeof(struct sockaddr_in6));
+      ret = reinterpret_cast<struct sockaddr *>(addr);
+    }
+  }
+
+  return ret;
+}
diff --git a/plugins/experimental/realip/pp.h b/plugins/experimental/realip/pp.h
new file mode 100644
index 0000000000..1c99c8396e
--- /dev/null
+++ b/plugins/experimental/realip/pp.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <yaml-cpp/yaml.h>
+
+#include "ts/ts.h"
+#include "address_source.h"
+
+class ProxyProtocolAddressSource : public AddressSource
+{
+public:
+  ProxyProtocolAddressSource(YAML::Node config);
+  ~ProxyProtocolAddressSource() = default;
+  bool             verify(TSHttpTxn txnp) override;
+  struct sockaddr *get_address(TSHttpTxn txnp, struct sockaddr_storage *addr) 
override;
+
+private:
+};
diff --git a/plugins/experimental/realip/realip.cc 
b/plugins/experimental/realip/realip.cc
new file mode 100644
index 0000000000..c0cb60f22a
--- /dev/null
+++ b/plugins/experimental/realip/realip.cc
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+#include <unistd.h>
+#include <cstdlib>
+#include <cstring>
+
+#include <yaml-cpp/yaml.h>
+
+#include "ts/ts.h"
+#include "tscore/ink_config.h"
+
+#include "realip.h"
+#include "address_source.h"
+#include "address_setter.h"
+
+DbgCtl dbg_ctl{PLUGIN_NAME};
+
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = (char *)PLUGIN_NAME;
+  info.vendor_name   = (char *)"Apache Software Foundation";
+  info.support_email = (char *)"[email protected]";
+
+  if (TS_SUCCESS != TSPluginRegister(&info)) {
+    TSError("[%s] plugin registration failed", PLUGIN_NAME);
+    return;
+  }
+
+  if (argc == 2) {
+    try {
+      YAML::Node     config = YAML::LoadFile(argv[1]);
+      AddressSource *source = AddressSourceBuilder::build(config);
+      if (source == nullptr) {
+        TSError("[%s] Failed to initialize an address source", PLUGIN_NAME);
+        return;
+      }
+      AddressSetter::set_source(source);
+      auto cont = TSContCreate(AddressSetter::event_handler, TSMutexCreate());
+      TSReleaseAssert(cont);
+      TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, cont);
+    } catch (YAML::BadFile const &e) {
+      TSError("[%s] Cannot load configuration file: %s.", PLUGIN_NAME, 
e.what());
+    } catch (std::exception const &e) {
+      TSError("[%s] Unknown error while loading configuration file: %s.", 
PLUGIN_NAME, e.what());
+    }
+  } else {
+    TSError("[%s] Usage: realip.so <config.yaml>", PLUGIN_NAME);
+  }
+}
diff --git a/plugins/experimental/realip/realip.h 
b/plugins/experimental/realip/realip.h
new file mode 100644
index 0000000000..2fda5bd4f9
--- /dev/null
+++ b/plugins/experimental/realip/realip.h
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include "ts/ts.h"
+
+constexpr char const PLUGIN_NAME[] = "realip";
+extern DbgCtl        dbg_ctl;
diff --git a/plugins/experimental/realip/simple.cc 
b/plugins/experimental/realip/simple.cc
new file mode 100644
index 0000000000..d984d43787
--- /dev/null
+++ b/plugins/experimental/realip/simple.cc
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+#include "realip.h"
+#include "simple.h"
+
+SimpleAddressSource::SimpleAddressSource(YAML::Node config)
+{
+  if (auto header_node = config["header"]; header_node) {
+    header_name = header_node.as<std::string>();
+    Dbg(dbg_ctl, "Header name: %s", header_name.c_str());
+  }
+
+  if (auto list_node = config["trustedAddress"]; list_node) {
+    for (YAML::const_iterator it = list_node.begin(); it != list_node.end(); 
++it) {
+      std::string item = it->as<std::string>();
+      if (swoc::IPRange r; r.load(item)) {
+        Dbg(dbg_ctl, "Adding %s to IP range set", item.c_str());
+        ip_range_set.mark(r);
+      }
+    }
+  }
+}
+
+bool
+SimpleAddressSource::verify(TSHttpTxn txnp)
+{
+  return ip_range_set.contains(swoc::IPAddr(TSHttpTxnClientAddrGet(txnp)));
+}
+
+struct sockaddr *
+SimpleAddressSource::get_address(TSHttpTxn txnp, struct sockaddr_storage *addr)
+{
+  TSMBuffer        bufp;
+  TSMLoc           hdr_loc;
+  TSMLoc           field_loc;
+  struct sockaddr *ret = nullptr;
+
+  if (TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+    Dbg(dbg_ctl, "Failed to get client request");
+    return nullptr;
+  }
+
+  if (header_name.empty()) {
+    return nullptr;
+  }
+
+  field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, header_name.c_str(), 
header_name.size());
+  if (field_loc) {
+    int         value_len;
+    const char *value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, 
-1, &value_len);
+    if (inet_pton46({value, 
static_cast<std::string_view::size_type>(value_len)}, addr) == 1) {
+      ret = reinterpret_cast<struct sockaddr *>(addr);
+    }
+    TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+  } else {
+    Dbg(dbg_ctl, "Failed to find %s header", header_name.c_str());
+  }
+  TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc);
+
+  return ret;
+}
diff --git a/plugins/experimental/realip/simple.h 
b/plugins/experimental/realip/simple.h
new file mode 100644
index 0000000000..566b432beb
--- /dev/null
+++ b/plugins/experimental/realip/simple.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include "address_source.h"
+#include "swoc/IPRange.h"
+
+class SimpleAddressSource : public AddressSource
+{
+public:
+  SimpleAddressSource(YAML::Node config);
+  ~SimpleAddressSource() = default;
+  bool             verify(TSHttpTxn txnp) override;
+  struct sockaddr *get_address(TSHttpTxn txnp, struct sockaddr_storage *addr) 
override;
+
+private:
+  std::string      header_name;
+  swoc::IPRangeSet ip_range_set;
+};
diff --git a/plugins/header_rewrite/conditions.cc 
b/plugins/header_rewrite/conditions.cc
index 74ecdc215d..696088e03d 100644
--- a/plugins/header_rewrite/conditions.cc
+++ b/plugins/header_rewrite/conditions.cc
@@ -1758,6 +1758,9 @@ getClientAddr(TSHttpTxn txnp, int txn_private_slot)
     TSVConnPPInfoGet(TSHttpSsnClientVConnGet(TSHttpTxnSsnGet(txnp)), 
TS_PP_INFO_SRC_ADDR, reinterpret_cast<const char **>(&addr),
                      &addr_len);
     break;
+  case IP_SRC_PLUGIN:
+    TSHttpTxnVerifiedAddrGet(txnp, &addr);
+    break;
   default:
     Dbg(pi_dbg_ctl, "Unknown IP source (%d) was specified", 
private_data.ip_source);
     addr = TSHttpTxnClientAddrGet(txnp);
diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index e26593651f..136e229a62 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -464,6 +464,8 @@ TSPluginInit(int argc, const char *argv[])
         header_rewrite_ns::inboundIpSource = IP_SRC_PEER;
       } else if (strcmp(optarg, "PROXY") == 0) {
         header_rewrite_ns::inboundIpSource = IP_SRC_PROXY;
+      } else if (strcmp(optarg, "PLUGIN") == 0) {
+        header_rewrite_ns::inboundIpSource = IP_SRC_PLUGIN;
       } else {
         TSError("[%s] Unknown value for inbound-ip-source parameter: %s", 
PLUGIN_NAME, optarg);
       }
diff --git a/plugins/header_rewrite/operators.cc 
b/plugins/header_rewrite/operators.cc
index 66857721a3..f6fff45540 100644
--- a/plugins/header_rewrite/operators.cc
+++ b/plugins/header_rewrite/operators.cc
@@ -1218,6 +1218,8 @@ OperatorSetPluginCntl::initialize(Parser &p)
       _value = IP_SRC_PEER;
     } else if (value == "PROXY") {
       _value = IP_SRC_PROXY;
+    } else if (value == "PLUGIN") {
+      _value = IP_SRC_PLUGIN;
     } else {
       TSError("[%s] Unknown value for INBOUND_IP_SOURCE control: %s", 
PLUGIN_NAME, value.c_str());
     }
diff --git a/plugins/header_rewrite/statement.h 
b/plugins/header_rewrite/statement.h
index 601157cc63..bb7a796687 100644
--- a/plugins/header_rewrite/statement.h
+++ b/plugins/header_rewrite/statement.h
@@ -238,8 +238,8 @@ union PrivateSlotData {
 enum { TIMEZONE_LOCAL, TIMEZONE_GMT };
 
 enum {
-  IP_SRC_PEER,  // Immediate connection
-  IP_SRC_PROXY, // PROXY protocl
+  IP_SRC_PEER,   // Immediate connection
+  IP_SRC_PROXY,  // PROXY protocl
+  IP_SRC_PLUGIN, // Plugin
   // IP_SRC_FORWARDED,  // Forwarded header field (TS core needs to support 
the header first. It can be done by a plugin as well.)
-  // IP_SRC_PLUGIN  // Plugin (Needs TS API to set and get a verified client 
IP address)
 };
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index 8987180a32..647d04e1e5 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -4288,6 +4288,37 @@ TSHttpTxnCacheLookupStatusSet(TSHttpTxn txnp, int 
cachelookup)
   return TS_SUCCESS;
 }
 
+TSReturnCode
+TSHttpTxnVerifiedAddrSet(TSHttpTxn txnp, const struct sockaddr *addr)
+{
+  sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
+  sdk_assert(sdk_sanity_check_null_ptr((void *)addr) == TS_SUCCESS);
+
+  HttpSM           *sm     = reinterpret_cast<HttpSM *>(txnp);
+  ProxyTransaction *prxtxn = sm->get_ua_txn();
+
+  prxtxn->set_verified_client_addr(addr);
+
+  return TS_SUCCESS;
+}
+
+TSReturnCode
+TSHttpTxnVerifiedAddrGet(TSHttpTxn txnp, const struct sockaddr **addr)
+{
+  sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);
+  sdk_assert(sdk_sanity_check_null_ptr((void *)addr) == TS_SUCCESS);
+
+  HttpSM           *sm     = reinterpret_cast<HttpSM *>(txnp);
+  ProxyTransaction *prxtxn = sm->get_ua_txn();
+
+  *addr = prxtxn->get_verified_client_addr();
+  if ((*addr)->sa_family == AF_UNSPEC) {
+    return TS_ERROR;
+  }
+
+  return TS_SUCCESS;
+}
+
 TSReturnCode
 TSHttpTxnInfoIntGet(TSHttpTxn txnp, TSHttpTxnInfoKey key, TSMgmtInt *value)
 {
diff --git a/src/proxy/IPAllow.cc b/src/proxy/IPAllow.cc
index 5aaab3d8e6..56c52a621d 100644
--- a/src/proxy/IPAllow.cc
+++ b/src/proxy/IPAllow.cc
@@ -232,6 +232,9 @@ IpAllow::IpAllow(const char *ip_allow_config_var, const 
char *ip_categories_conf
       } else if (subject_sv == "PROXY") {
         subjects[i] = Subject::PROXY;
         ++i;
+      } else if (subject_sv == "PLUGIN") {
+        subjects[i] = Subject::PLUGIN;
+        ++i;
       } else {
         Dbg(dbg_ctl_ip_allow, "Unknown subject %.*s was ignored", 
static_cast<int>(subject_sv.length()), subject_sv.data());
       }
diff --git a/src/proxy/http/remap/UrlRewrite.cc 
b/src/proxy/http/remap/UrlRewrite.cc
index 1926682c56..c3b0375dde 100644
--- a/src/proxy/http/remap/UrlRewrite.cc
+++ b/src/proxy/http/remap/UrlRewrite.cc
@@ -457,6 +457,10 @@ UrlRewrite::PerformACLFiltering(HttpTransact::State *s, 
const url_mapping *const
         src_addr   = &pp_info.src_addr;
         local_addr = &pp_info.dst_addr;
         break;
+      } else if (IpAllow::Subject::PLUGIN == IpAllow::subjects[i]) {
+        src_addr   = reinterpret_cast<const IpEndpoint 
*>(s->state_machine->get_ua_txn()->get_verified_client_addr());
+        local_addr = &s->client_info.dst_addr;
+        break;
       }
     }
 
diff --git a/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt 
b/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
index d5e3a088fa..e0f1e0030f 100644
--- a/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
+++ b/tests/gold_tests/pluginTest/tsapi/CMakeLists.txt
@@ -19,3 +19,4 @@ add_autest_plugin(test_tsapi test_tsapi.cc)
 add_autest_plugin(test_TSHttpTxnServerAddrSet test_TSHttpTxnServerAddrSet.cc)
 add_autest_plugin(test_TSHttpSsnInfo test_TSHttpSsnInfo.cc)
 add_autest_plugin(test_TSVConnPPInfo test_TSVConnPPInfo.cc)
+add_autest_plugin(test_TSHttpTxnVerifiedAddr test_TSHttpTxnVerifiedAddr.cc)
diff --git a/tests/gold_tests/pluginTest/tsapi/hrw_verified_addr.conf 
b/tests/gold_tests/pluginTest/tsapi/hrw_verified_addr.conf
new file mode 100644
index 0000000000..78d57b29fc
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/hrw_verified_addr.conf
@@ -0,0 +1,19 @@
+#
+# 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.
+
+cond %{SEND_RESPONSE_HDR_HOOK}
+  set-header ip %{INBOUND:REMOTE-ADDR}
diff --git a/tests/gold_tests/pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.cc 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.cc
new file mode 100644
index 0000000000..625bf8ed18
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.cc
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#include <fstream>
+#include <cstdlib>
+#include <arpa/inet.h>
+
+#include <ts/ts.h>
+
+namespace
+{
+#define PINAME "test_TSHttpTxnVerifiedAddr"
+char PIName[] = PINAME;
+
+DbgCtl dbg_ctl{PIName};
+
+void
+handle_txn_start(TSHttpTxn txn)
+{
+  struct sockaddr_in addr = {};
+  addr.sin_family         = AF_INET;
+  addr.sin_port           = 0;
+  addr.sin_addr           = {.s_addr = 0x01010101}; // 1.1.1.1
+  TSHttpTxnVerifiedAddrSet(txn, reinterpret_cast<struct sockaddr *>(&addr));
+  TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
+}
+
+int
+globalContFunc(TSCont, TSEvent event, void *eventData)
+{
+  Dbg(dbg_ctl, "Global: event=%s(%d) eventData=%p", 
TSHttpEventNameLookup(event), event, eventData);
+
+  switch (event) {
+  case TS_EVENT_HTTP_TXN_START:
+    handle_txn_start(static_cast<TSHttpTxn>(eventData));
+    break;
+  default:
+    break;
+  } // end switch
+
+  return 0;
+}
+
+TSCont gCont;
+
+} // end anonymous namespace
+
+void
+TSPluginInit(int /* argc ATS_UNUSED */, const char ** /* argv ATS_UNUSED */)
+{
+  TSPluginRegistrationInfo info;
+
+  info.plugin_name   = PIName;
+  info.vendor_name   = "Apache Software Foundation";
+  info.support_email = "[email protected]";
+
+  if (TSPluginRegister(&info) != TS_SUCCESS) {
+    TSError(PINAME ": Plugin registration failed");
+
+    return;
+  }
+
+  TSMutex mtx = TSMutexCreate();
+  gCont       = TSContCreate(globalContFunc, mtx);
+  TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, gCont);
+}
diff --git 
a/tests/gold_tests/pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.test.py 
b/tests/gold_tests/pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.test.py
new file mode 100644
index 0000000000..69196dfacc
--- /dev/null
+++ b/tests/gold_tests/pluginTest/tsapi/test_TSHttpTxnVerifiedAddr.test.py
@@ -0,0 +1,70 @@
+'''
+'''
+#  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.
+
+import os
+
+Test.Summary = '''
+Test TS API to get and set a verified address
+'''
+
+Test.ContinueOnFail = True
+
+# ----
+# Setup Origin Server
+# ----
+httpbin = Test.MakeHttpBinServer("httpbin")
+
+# ----
+# Setup ATS
+# ----
+ts = Test.MakeATSProcess("ts", enable_tls=True)
+
+# add ssl materials like key, certificates for the server
+ts.addDefaultSSLFiles()
+
+ts.Disk.remap_config.AddLines([f'map /httpbin/ 
http://127.0.0.1:{httpbin.Variables.Port}/'])
+
+ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem 
ssl_key_name=server.key')
+
+Test.PrepareTestPlugin(
+    os.path.join(Test.Variables.AtsBuildGoldTestsDir, 'pluginTest', 'tsapi', 
'.libs', 'test_TSHttpTxnVerifiedAddr.so'), ts)
+
+ts.Setup.CopyAs('hrw_verified_addr.conf', Test.RunDirectory)
+ts.Disk.plugin_config.AddLine(f'header_rewrite.so --inbound-ip-source=PLUGIN 
{Test.RunDirectory}/hrw_verified_addr.conf')
+
+ts.Disk.records_config.update(
+    {
+        'proxy.config.diags.debug.enabled': 1,
+        'proxy.config.diags.debug.tags': 'http|test_TSHttpTxnVerifiedAddr',
+        'proxy.config.ssl.server.cert.path': f'{ts.Variables.SSLDir}',
+        'proxy.config.ssl.server.private_key.path': f'{ts.Variables.SSLDir}'
+    })
+
+# ----
+# Test Cases
+# ----
+
+tr = Test.AddTestRun()
+tr.MakeCurlCommand(f'-v http://127.0.0.1:{ts.Variables.port}/httpbin/get', 
ts=ts)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.StartBefore(httpbin, 
ready=When.PortOpen(httpbin.Variables.Port))
+tr.Processes.Default.StartBefore(Test.Processes.ts)
+tr.Processes.Default.Streams.stderr.Content = Testers.ContainsExpression(
+    "ip: 1.1.1.1", "Verifiy header_rewrite picked the verified address")
+tr.StillRunningAfter = httpbin
+tr.StillRunningAfter = ts


Reply via email to