You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by bc...@apache.org on 2022/06/14 17:02:39 UTC

[trafficserver] branch 9.1.x updated (7a2eed0d1 -> 7f0a0df20)

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

bcall pushed a change to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


    from 7a2eed0d1 Do not modify Transfer-Encoding header on retry (#8899)
     new 6c94e2fc9 Revert "Update to proxy-verifier-v2.3.1 (#8753)"
     new 5e4a4c246 Fix Multiplexer POST/PUT Body Handling (#8439)
     new 3f18eb34f Update to Proxy Verifier version v2.3.0 (#8608)
     new 7f0a0df20 Update to proxy-verifier-v2.3.1 (#8753)

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 plugins/multiplexer/dispatch.cc                    |   9 +-
 plugins/multiplexer/dispatch.h                     |   8 +-
 .../chunked_encoding/bad_chunked_encoding.test.py  |  14 +-
 .../gold/verifier_client_chunked.gold              |  14 +-
 .../replays/malformed_chunked_header.replay.yaml   |   4 +-
 .../pluginTest/multiplexer/gold/multiplexer.gold   |   1 -
 .../pluginTest/multiplexer/multiplexer.test.py     | 224 ++++++++++++++++++---
 .../replays/multiplexer_copy.replay.yaml           | 113 +++++++++++
 .../multiplexer_copy_skip_post.replay.yaml}        |  32 +--
 .../replays/multiplexer_original.replay.yaml       | 122 +++++++++++
 .../multiplexer_original_skip_post.replay.yaml     | 122 +++++++++++
 11 files changed, 601 insertions(+), 62 deletions(-)
 delete mode 100644 tests/gold_tests/pluginTest/multiplexer/gold/multiplexer.gold
 create mode 100644 tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy.replay.yaml
 copy tests/gold_tests/{dns/replay/single_transaction.replay.yaml => pluginTest/multiplexer/replays/multiplexer_copy_skip_post.replay.yaml} (66%)
 create mode 100644 tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original.replay.yaml
 create mode 100644 tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original_skip_post.replay.yaml


[trafficserver] 04/04: Update to proxy-verifier-v2.3.1 (#8753)

Posted by bc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bcall pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 7f0a0df2025fd81a3b573ba8b5c7b0ef09831830
Author: Brian Neradt <br...@verizonmedia.com>
AuthorDate: Wed Mar 23 12:24:05 2022 -0500

    Update to proxy-verifier-v2.3.1 (#8753)
    
    Proxy Verifier version 2.3.1 actively closes the connection when the
    proxy (ATS) returns a response with a "Connection: close" header. This
    improves the stability of the cache-request-method AuTest, wherein ATS,
    dependent upon packet timing, would sometimes return a "Connection:
    close" and follow it with a TCP FIN. The FIN would then be processed by
    the Verifier client after request headers were sent, which would
    interrupt the traffic flow and cause the test to fail. With Verifier
    version 2.3.1, this is avoided by the client actively closing the
    connection when ATS returns the intermittent "Connection: close".
    
    (cherry picked from commit b8b8154f44b595a5bd9e09ef0873cbac92949a79)
---
 tests/prepare_proxy_verifier.sh  | 2 +-
 tests/proxy-verifier-version.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/prepare_proxy_verifier.sh b/tests/prepare_proxy_verifier.sh
index b26c78d92..3c75883ac 100755
--- a/tests/prepare_proxy_verifier.sh
+++ b/tests/prepare_proxy_verifier.sh
@@ -27,7 +27,7 @@ pv_dir="${pv_name}-${pv_version}"
 pv_tar_filename="${pv_dir}.tar.gz"
 pv_tar="${pv_top_dir}/${pv_tar_filename}"
 pv_tar_url="https://ci.trafficserver.apache.org/bintray/${pv_tar_filename}"
-expected_sha1="5c7b5a0e105321cc1627fcdc80b2bf16b44033af"
+expected_sha1="d602c299d1f1336c4970529ab7fef7afbbb8723b"
 pv_client="${bin_dir}/verifier-client"
 pv_server="${bin_dir}/verifier-server"
 TAR=${TAR:-tar}
diff --git a/tests/proxy-verifier-version.txt b/tests/proxy-verifier-version.txt
index b1d18bc43..aaf7425f4 100644
--- a/tests/proxy-verifier-version.txt
+++ b/tests/proxy-verifier-version.txt
@@ -1 +1 @@
-v2.3.0
+v2.3.1


[trafficserver] 03/04: Update to Proxy Verifier version v2.3.0 (#8608)

Posted by bc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bcall pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 3f18eb34ff43c03c10c2051804e9671f07fd9998
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Tue Jan 18 11:13:59 2022 -0600

    Update to Proxy Verifier version v2.3.0 (#8608)
    
    This updates to the Proxy Verifier v2.3.0 release. This release updates
    to the new libswoc::Errata and comes with some logging improvements.
    Since the logs change, the test expectations for Proxy Verifier output
    is updated with this commit as well.
    
    (cherry picked from commit 9ca7618332673fdf2f86527e35fc304cf3989471)
---
 .../chunked_encoding/bad_chunked_encoding.test.py  | 14 ++++----
 .../gold/verifier_client_chunked.gold              | 14 ++++----
 .../replays/malformed_chunked_header.replay.yaml   |  4 +--
 .../pluginTest/multiplexer/multiplexer.test.py     | 38 +++++++++++-----------
 tests/prepare_proxy_verifier.sh                    |  2 +-
 tests/proxy-verifier-version.txt                   |  2 +-
 6 files changed, 38 insertions(+), 36 deletions(-)

diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
index d64daa5e3..71b0cb4ca 100644
--- a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
+++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
@@ -138,8 +138,8 @@ class MalformedChunkHeaderTest:
             "Header write for key 2 failed",
             "Verify that writing the second response failed.")
 
-        # ATS should close the connection before any body gets through.
-        # "abc" is the body sent for each of these chunked cases.
+        # ATS should close the connection before any body gets through. "abc"
+        # is the body sent by the client for each of these chunked cases.
         self.server.Streams.stdout += Testers.ExcludesExpression(
             "abc",
             "Verify that the body never got through.")
@@ -181,16 +181,16 @@ class MalformedChunkHeaderTest:
         # code from the verifier client.
         tr.Processes.Default.ReturnCode = 1
         tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-            "Failed HTTP/1 transaction with key=3",
+            "Failed HTTP/1 transaction with key: 3",
             "Verify that ATS closed the third transaction.")
         tr.Processes.Default.Streams.stdout += Testers.ContainsExpression(
-            "Failed HTTP/1 transaction with key=4",
+            "Failed HTTP/1 transaction with key: 4",
             "Verify that ATS closed the fourth transaction.")
 
-        # ATS should close the connection before any body gets through.
-        # "abc" is the body sent for each of these chunked cases.
+        # ATS should close the connection before any body gets through. "def"
+        # is the body sent by the server for each of these chunked cases.
         tr.Processes.Default.Streams.stdout += Testers.ExcludesExpression(
-            "abc",
+            "def",
             "Verify that the body never got through.")
 
     def run(self):
diff --git a/tests/gold_tests/chunked_encoding/gold/verifier_client_chunked.gold b/tests/gold_tests/chunked_encoding/gold/verifier_client_chunked.gold
index f2beb344a..ad7be3b97 100644
--- a/tests/gold_tests/chunked_encoding/gold/verifier_client_chunked.gold
+++ b/tests/gold_tests/chunked_encoding/gold/verifier_client_chunked.gold
@@ -1,13 +1,15 @@
 ``
-- "connection": "keep-alive"
+connection: keep-alive
 ``
-- "Connection": "close"
+Connection: close
 ``
-``content: 0000000 0000001 0000002 0000003 
+``content:
+0000000 0000001 0000002 0000003 
 ``
-- "connection": "keep-alive"
+connection: keep-alive
 ``
-- "Connection": "close"
+Connection: close
 ``
-``content: 0000000 0000001 0000002 0000003 
+``content:
+0000000 0000001 0000002 0000003 
 ``
diff --git a/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml b/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml
index c6091ab4e..fd551941e 100644
--- a/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml
+++ b/tests/gold_tests/chunked_encoding/replays/malformed_chunked_header.replay.yaml
@@ -83,7 +83,7 @@ sessions:
         transfer: plain
         encoding: uri
         # Chunk header sizes are in hex, so a size of `z` is malformed.
-        data: z%0D%0Aabc%0D%0A0%0D%0A%0D%0A
+        data: z%0D%0Adef%0D%0A0%0D%0A%0D%0A
 
 - transactions:
   - client-request:
@@ -106,4 +106,4 @@ sessions:
         transfer: plain
         encoding: uri
         # Super large chunk header, larger than will fit in an int.
-        data: 111111113%0D%0Aabc%0D%0A0%0D%0A%0D%0A
+        data: 111111113%0D%0Adef%0D%0A0%0D%0A%0D%0A
diff --git a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
index df1ebd9dc..4fcb1437b 100644
--- a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
+++ b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
@@ -55,46 +55,46 @@ class MultiplexerTestBase:
 
         # The origin should never receive "X-Multiplexer: copy"
         self.server_origin.Streams.All += Testers.ExcludesExpression(
-            '"X-Multiplexer": "copy"',
+            'X-Multiplexer: copy',
             'Verify the original server target never receives a "copy".')
 
         # Nor should the multiplexed hosts receive an "original" X-Multiplexer value.
         self.server_http.Streams.All += Testers.ExcludesExpression(
-            '"X-Multiplexer": "original"',
+            'X-Multiplexer: original',
             'Verify the HTTP multiplexed host does not receive an "original".')
         self.server_https.Streams.All += Testers.ExcludesExpression(
-            '"X-Multiplexer": "original"',
+            'X-Multiplexer: original',
             'Verify the HTTPS multiplexed host does not receive an "original".')
 
         # In addition, the original server should always receive the POST and
         # PUT requests.
         self.server_origin.Streams.All += Testers.ContainsExpression(
-            '"uuid": "POST"',
+            'uuid: POST',
             "Verify the client's original target received the POST transaction.")
         self.server_origin.Streams.All += Testers.ContainsExpression(
-            '"uuid": "PUT"',
+            'uuid: PUT',
             "Verify the client's original target received the PUT transaction.")
 
         # Under all configurations, the GET request should be multiplexed.
         self.server_origin.Streams.All += Testers.ContainsExpression(
-            '"X-Multiplexer": "original"',
+            'X-Multiplexer: original',
             'Verify the client\'s original target received the "original" request.')
         self.server_origin.Streams.All += Testers.ContainsExpression(
-            '"uuid": "GET"',
+            'uuid: GET',
             "Verify the client's original target received the GET request.")
 
         self.server_http.Streams.All += Testers.ContainsExpression(
-            '"X-Multiplexer": "copy"',
+            'X-Multiplexer: copy',
             'Verify the HTTP server received a "copy" of the request.')
         self.server_http.Streams.All += Testers.ContainsExpression(
-            '"uuid": "GET"',
+            'uuid: GET',
             "Verify the HTTP server received the GET request.")
 
         self.server_https.Streams.All += Testers.ContainsExpression(
-            '"X-Multiplexer": "copy"',
+            'X-Multiplexer: copy',
             'Verify the HTTPS server received a "copy" of the request.')
         self.server_https.Streams.All += Testers.ContainsExpression(
-            '"uuid": "GET"',
+            'uuid: GET',
             "Verify the HTTPS server received the GET request.")
 
         # Verify that the HTTPS server receives a TLS connection.
@@ -168,18 +168,18 @@ class MultiplexerTest(MultiplexerTestBase):
         # Both of the multiplexed hosts should receive the POST because skip_post
         # is disabled.
         self.server_http.Streams.All += Testers.ContainsExpression(
-            '"uuid": "POST"',
+            'uuid: POST',
             "Verify the HTTP server received the POST request.")
         self.server_https.Streams.All += Testers.ContainsExpression(
-            '"uuid": "POST"',
+            'uuid: POST',
             "Verify the HTTPS server received the POST request.")
 
         # Same with PUT
         self.server_http.Streams.All += Testers.ContainsExpression(
-            '"uuid": "PUT"',
+            'uuid: PUT',
             "Verify the HTTP server received the PUT request.")
         self.server_https.Streams.All += Testers.ContainsExpression(
-            '"uuid": "PUT"',
+            'uuid: PUT',
             "Verify the HTTPS server received the PUT request.")
 
 
@@ -203,18 +203,18 @@ class MultiplexerSkipPostTest(MultiplexerTestBase):
         # Neither of the multiplexed hosts should receive the POST because skip_post
         # is enabled.
         self.server_http.Streams.All += Testers.ExcludesExpression(
-            '"uuid": "POST"',
+            'uuid: POST',
             "Verify the HTTP server did not receive the POST request.")
         self.server_https.Streams.All += Testers.ExcludesExpression(
-            '"uuid": "POST"',
+            'uuid: POST',
             "Verify the HTTPS server did not receive the POST request.")
 
         # Same with PUT.
         self.server_http.Streams.All += Testers.ExcludesExpression(
-            '"uuid": "PUT"',
+            'uuid: PUT',
             "Verify the HTTP server did not receive the PUT request.")
         self.server_https.Streams.All += Testers.ExcludesExpression(
-            '"uuid": "PUT"',
+            'uuid: PUT',
             "Verify the HTTPS server did not receive the PUT request.")
 
 
diff --git a/tests/prepare_proxy_verifier.sh b/tests/prepare_proxy_verifier.sh
index ff74c3860..b26c78d92 100755
--- a/tests/prepare_proxy_verifier.sh
+++ b/tests/prepare_proxy_verifier.sh
@@ -27,7 +27,7 @@ pv_dir="${pv_name}-${pv_version}"
 pv_tar_filename="${pv_dir}.tar.gz"
 pv_tar="${pv_top_dir}/${pv_tar_filename}"
 pv_tar_url="https://ci.trafficserver.apache.org/bintray/${pv_tar_filename}"
-expected_sha1="d9a02aedae76d4952784c67716fddba0df274a28"
+expected_sha1="5c7b5a0e105321cc1627fcdc80b2bf16b44033af"
 pv_client="${bin_dir}/verifier-client"
 pv_server="${bin_dir}/verifier-server"
 TAR=${TAR:-tar}
diff --git a/tests/proxy-verifier-version.txt b/tests/proxy-verifier-version.txt
index 6c8b2a3c3..b1d18bc43 100644
--- a/tests/proxy-verifier-version.txt
+++ b/tests/proxy-verifier-version.txt
@@ -1 +1 @@
-v2.2.2
+v2.3.0


[trafficserver] 01/04: Revert "Update to proxy-verifier-v2.3.1 (#8753)"

Posted by bc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bcall pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 6c94e2fc923b2fab59c28e0f5506b66b3e286b52
Author: Bryan Call <bc...@apache.org>
AuthorDate: Tue Jun 14 09:45:41 2022 -0700

    Revert "Update to proxy-verifier-v2.3.1 (#8753)"
    
    This reverts commit 4f68da2b36ca2874c9ea682d6a68cd7a90e7ebfa.
---
 tests/prepare_proxy_verifier.sh  | 2 +-
 tests/proxy-verifier-version.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/prepare_proxy_verifier.sh b/tests/prepare_proxy_verifier.sh
index 3c75883ac..ff74c3860 100755
--- a/tests/prepare_proxy_verifier.sh
+++ b/tests/prepare_proxy_verifier.sh
@@ -27,7 +27,7 @@ pv_dir="${pv_name}-${pv_version}"
 pv_tar_filename="${pv_dir}.tar.gz"
 pv_tar="${pv_top_dir}/${pv_tar_filename}"
 pv_tar_url="https://ci.trafficserver.apache.org/bintray/${pv_tar_filename}"
-expected_sha1="d602c299d1f1336c4970529ab7fef7afbbb8723b"
+expected_sha1="d9a02aedae76d4952784c67716fddba0df274a28"
 pv_client="${bin_dir}/verifier-client"
 pv_server="${bin_dir}/verifier-server"
 TAR=${TAR:-tar}
diff --git a/tests/proxy-verifier-version.txt b/tests/proxy-verifier-version.txt
index aaf7425f4..6c8b2a3c3 100644
--- a/tests/proxy-verifier-version.txt
+++ b/tests/proxy-verifier-version.txt
@@ -1 +1 @@
-v2.3.1
+v2.2.2


[trafficserver] 02/04: Fix Multiplexer POST/PUT Body Handling (#8439)

Posted by bc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bcall pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 5e4a4c24621051af904598484d4d32f603b6661f
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Mon Oct 25 10:32:56 2021 -0500

    Fix Multiplexer POST/PUT Body Handling (#8439)
    
    The previous Multiplexer Plugin test only verified that the plugin would
    load without error. This updates the test to verify it can multiplex to
    multiple hosts, exercising both HTTP and HTTPS connections to the
    multiplexed hosts.
    
    While doing this, I added POST and PUT request method tests. This showed
    that the plugin could not handle request bodies and simply failed a
    misconfigured assertion when the request body was processed. This change
    also fixes that bug in the plugin.
    
    (cherry picked from commit 636d42af56779a6bac4a774745f688f7702247f6)
---
 plugins/multiplexer/dispatch.cc                    |   9 +-
 plugins/multiplexer/dispatch.h                     |   8 +-
 .../pluginTest/multiplexer/gold/multiplexer.gold   |   1 -
 .../pluginTest/multiplexer/multiplexer.test.py     | 224 ++++++++++++++++++---
 .../replays/multiplexer_copy.replay.yaml           | 113 +++++++++++
 .../replays/multiplexer_copy_skip_post.replay.yaml |  55 +++++
 .../replays/multiplexer_original.replay.yaml       | 122 +++++++++++
 .../multiplexer_original_skip_post.replay.yaml     | 122 +++++++++++
 8 files changed, 620 insertions(+), 34 deletions(-)

diff --git a/plugins/multiplexer/dispatch.cc b/plugins/multiplexer/dispatch.cc
index 493501ec9..849204b8b 100644
--- a/plugins/multiplexer/dispatch.cc
+++ b/plugins/multiplexer/dispatch.cc
@@ -86,8 +86,13 @@ copy(const TSIOBufferReader &r, const TSIOBuffer b)
     const void *const pointer = TSIOBufferBlockReadStart(block, r, &size);
 
     if (pointer != nullptr && size > 0) {
-      CHECK(TSIOBufferWrite(b, pointer, size) == size);
-      length += size;
+      auto const num_written = TSIOBufferWrite(b, pointer, size);
+      if (num_written != size) {
+        TSError("[" PLUGIN_TAG "] did not write the expected number of body bytes. "
+                "Wrote: %" PRId64 ", expected: %" PRId64,
+                num_written, size);
+      }
+      length += num_written;
     }
   }
 
diff --git a/plugins/multiplexer/dispatch.h b/plugins/multiplexer/dispatch.h
index 4cba3919a..652e148f5 100644
--- a/plugins/multiplexer/dispatch.h
+++ b/plugins/multiplexer/dispatch.h
@@ -49,10 +49,10 @@
 #else
 
 // Check if expression X returns a value that implicitly converts to bool false (such as TS_SUCCESS).
-#define CHECK(X)                \
-  {                             \
-    static_assert(!TS_SUCCESS); \
-    assert(!(X));               \
+#define CHECK(X)                                         \
+  {                                                      \
+    const TSReturnCode r = static_cast<TSReturnCode>(X); \
+    assert(r == TS_SUCCESS);                             \
   }
 
 #endif
diff --git a/tests/gold_tests/pluginTest/multiplexer/gold/multiplexer.gold b/tests/gold_tests/pluginTest/multiplexer/gold/multiplexer.gold
deleted file mode 100644
index 39572941d..000000000
--- a/tests/gold_tests/pluginTest/multiplexer/gold/multiplexer.gold
+++ /dev/null
@@ -1 +0,0 @@
-``DIAG: (multiplexer)``
diff --git a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
index 18a0ac73c..df1ebd9dc 100644
--- a/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
+++ b/tests/gold_tests/pluginTest/multiplexer/multiplexer.test.py
@@ -16,37 +16,207 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+import os
+
 Test.Summary = '''
-Test experimental/multiplexer.
+Test the Multiplexer plugin.
 '''
 
 Test.SkipUnless(
     Condition.PluginExists('multiplexer.so')
 )
-Test.ContinueOnFail = False
-# Define default ATS
-ts = Test.MakeATSProcess("ts")
-server = Test.MakeOriginServer("server")
-
-request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
-response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
-server.addResponse("sessionfile.log", request_header, response_header)
-
-
-ts.Disk.records_config.update({
-    'proxy.config.diags.debug.enabled': 1,
-    'proxy.config.diags.debug.tags': 'multiplexer',
-})
-ts.Disk.remap_config.AddLine(
-    'map http://www.example.com http://127.0.0.1:{0} @plugin=multiplexer.so'.format(server.Variables.Port)
-)
 
-# For now, just make sure the plugin loads without error.
-tr = Test.AddTestRun()
-tr.Processes.Default.Command = 'curl --silent --proxy 127.0.0.1:{0} "http://www.example.com" -H "Proxy-Connection: close"'.format(
-    ts.Variables.port)
-tr.Processes.Default.ReturnCode = 0
-tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
-tr.Processes.Default.StartBefore(Test.Processes.ts)
-ts.Streams.stderr = "gold/multiplexer.gold"
-tr.StillRunningAfter = ts
+
+class MultiplexerTestBase:
+    """
+    Encapsulates the base configuration used by each test.
+    """
+
+    client_counter = 0
+    server_counter = 0
+    ts_counter = 0
+
+    def __init__(self, replay_file, multiplexed_host_replay_file, skip_post):
+        self.replay_file = replay_file
+        self.multiplexed_host_replay_file = multiplexed_host_replay_file
+
+        self.setupServers()
+        self.setupTS(skip_post)
+
+    def setupServers(self):
+        counter = MultiplexerTestBase.server_counter
+        MultiplexerTestBase.server_counter += 1
+        self.server_origin = Test.MakeVerifierServerProcess(
+            f"server_origin_{counter}", self.replay_file)
+        self.server_http = Test.MakeVerifierServerProcess(
+            f"server_http_{counter}", self.multiplexed_host_replay_file)
+        self.server_https = Test.MakeVerifierServerProcess(
+            f"server_https_{counter}", self.multiplexed_host_replay_file)
+
+        # The origin should never receive "X-Multiplexer: copy"
+        self.server_origin.Streams.All += Testers.ExcludesExpression(
+            '"X-Multiplexer": "copy"',
+            'Verify the original server target never receives a "copy".')
+
+        # Nor should the multiplexed hosts receive an "original" X-Multiplexer value.
+        self.server_http.Streams.All += Testers.ExcludesExpression(
+            '"X-Multiplexer": "original"',
+            'Verify the HTTP multiplexed host does not receive an "original".')
+        self.server_https.Streams.All += Testers.ExcludesExpression(
+            '"X-Multiplexer": "original"',
+            'Verify the HTTPS multiplexed host does not receive an "original".')
+
+        # In addition, the original server should always receive the POST and
+        # PUT requests.
+        self.server_origin.Streams.All += Testers.ContainsExpression(
+            '"uuid": "POST"',
+            "Verify the client's original target received the POST transaction.")
+        self.server_origin.Streams.All += Testers.ContainsExpression(
+            '"uuid": "PUT"',
+            "Verify the client's original target received the PUT transaction.")
+
+        # Under all configurations, the GET request should be multiplexed.
+        self.server_origin.Streams.All += Testers.ContainsExpression(
+            '"X-Multiplexer": "original"',
+            'Verify the client\'s original target received the "original" request.')
+        self.server_origin.Streams.All += Testers.ContainsExpression(
+            '"uuid": "GET"',
+            "Verify the client's original target received the GET request.")
+
+        self.server_http.Streams.All += Testers.ContainsExpression(
+            '"X-Multiplexer": "copy"',
+            'Verify the HTTP server received a "copy" of the request.')
+        self.server_http.Streams.All += Testers.ContainsExpression(
+            '"uuid": "GET"',
+            "Verify the HTTP server received the GET request.")
+
+        self.server_https.Streams.All += Testers.ContainsExpression(
+            '"X-Multiplexer": "copy"',
+            'Verify the HTTPS server received a "copy" of the request.')
+        self.server_https.Streams.All += Testers.ContainsExpression(
+            '"uuid": "GET"',
+            "Verify the HTTPS server received the GET request.")
+
+        # Verify that the HTTPS server receives a TLS connection.
+        self.server_https.Streams.All += Testers.ContainsExpression(
+            'Finished accept using TLSSession',
+            "Verify the HTTPS was indeed used by the HTTPS server.")
+
+    def setupTS(self, skip_post):
+        counter = MultiplexerTestBase.ts_counter
+        MultiplexerTestBase.ts_counter += 1
+        self.ts = Test.MakeATSProcess(f"ts_{counter}", enable_tls=True, enable_cache=False)
+        self.ts.addDefaultSSLFiles()
+        self.ts.Disk.records_config.update({
+            "proxy.config.ssl.server.cert.path": f'{self.ts.Variables.SSLDir}',
+            "proxy.config.ssl.server.private_key.path": f'{self.ts.Variables.SSLDir}',
+            "proxy.config.ssl.client.verify.server.policy": 'PERMISSIVE',
+            'proxy.config.ssl.keylog_file': '/tmp/tls_session_keys.txt',
+
+            'proxy.config.diags.debug.enabled': 1,
+            'proxy.config.diags.debug.tags': 'multiplexer',
+        })
+        self.ts.Disk.ssl_multicert_config.AddLine(
+            'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+        )
+        skip_remap_param = ''
+        if skip_post:
+            skip_remap_param = ' @pparam=proxy.config.multiplexer.skip_post_put=1'
+        self.ts.Disk.remap_config.AddLines([
+            f'map https://origin.server.com https://127.0.0.1:{self.server_origin.Variables.https_port} '
+            f'@plugin=multiplexer.so @pparam=nontls.server.com @pparam=tls.server.com'
+            f'{skip_remap_param}',
+
+            # Now create remap entries for the multiplexed hosts: one that
+            # verifies HTTP, and another that verifies HTTPS.
+            f'map http://nontls.server.com http://127.0.0.1:{self.server_http.Variables.http_port}',
+            f'map http://tls.server.com https://127.0.0.1:{self.server_https.Variables.https_port}',
+        ])
+
+    def run(self):
+        tr = Test.AddTestRun()
+        tr.Processes.Default.StartBefore(self.server_origin)
+        tr.Processes.Default.StartBefore(self.server_http)
+        tr.Processes.Default.StartBefore(self.server_https)
+        tr.Processes.Default.StartBefore(self.ts)
+
+        counter = MultiplexerTestBase.client_counter
+        MultiplexerTestBase.client_counter += 1
+        tr.AddVerifierClientProcess(
+            f"client_{counter}",
+            self.replay_file,
+            https_ports=[self.ts.Variables.ssl_port])
+
+
+class MultiplexerTest(MultiplexerTestBase):
+    """
+    Exercise multiplexing without skip_post configuration.
+    """
+
+    replay_file = os.path.join("replays", "multiplexer_original.replay.yaml")
+    multiplexed_host_replay_file = os.path.join("replays", "multiplexer_copy.replay.yaml")
+
+    def __init__(self):
+        super().__init__(
+            MultiplexerTest.replay_file,
+            MultiplexerTest.multiplexed_host_replay_file,
+            skip_post=False)
+
+    def setupServers(self):
+        super().setupServers()
+
+        # Both of the multiplexed hosts should receive the POST because skip_post
+        # is disabled.
+        self.server_http.Streams.All += Testers.ContainsExpression(
+            '"uuid": "POST"',
+            "Verify the HTTP server received the POST request.")
+        self.server_https.Streams.All += Testers.ContainsExpression(
+            '"uuid": "POST"',
+            "Verify the HTTPS server received the POST request.")
+
+        # Same with PUT
+        self.server_http.Streams.All += Testers.ContainsExpression(
+            '"uuid": "PUT"',
+            "Verify the HTTP server received the PUT request.")
+        self.server_https.Streams.All += Testers.ContainsExpression(
+            '"uuid": "PUT"',
+            "Verify the HTTPS server received the PUT request.")
+
+
+class MultiplexerSkipPostTest(MultiplexerTestBase):
+    """
+    Exercise multiplexing with skip_post configuration.
+    """
+
+    replay_file = os.path.join("replays", "multiplexer_original_skip_post.replay.yaml")
+    multiplexed_host_replay_file = os.path.join("replays", "multiplexer_copy_skip_post.replay.yaml")
+
+    def __init__(self):
+        super().__init__(
+            MultiplexerSkipPostTest.replay_file,
+            MultiplexerSkipPostTest.multiplexed_host_replay_file,
+            skip_post=True)
+
+    def setupServers(self):
+        super().setupServers()
+
+        # Neither of the multiplexed hosts should receive the POST because skip_post
+        # is enabled.
+        self.server_http.Streams.All += Testers.ExcludesExpression(
+            '"uuid": "POST"',
+            "Verify the HTTP server did not receive the POST request.")
+        self.server_https.Streams.All += Testers.ExcludesExpression(
+            '"uuid": "POST"',
+            "Verify the HTTPS server did not receive the POST request.")
+
+        # Same with PUT.
+        self.server_http.Streams.All += Testers.ExcludesExpression(
+            '"uuid": "PUT"',
+            "Verify the HTTP server did not receive the PUT request.")
+        self.server_https.Streams.All += Testers.ExcludesExpression(
+            '"uuid": "PUT"',
+            "Verify the HTTPS server did not receive the PUT request.")
+
+
+MultiplexerTest().run()
+MultiplexerSkipPostTest().run()
diff --git a/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy.replay.yaml b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy.replay.yaml
new file mode 100644
index 000000000..c4ceb9094
--- /dev/null
+++ b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy.replay.yaml
@@ -0,0 +1,113 @@
+#  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.
+
+meta:
+  version: "1.0"
+
+sessions:
+- protocol:
+  - name: http
+  - name: tls
+  - name: tcp
+  - name: ip
+
+  transactions:
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      url: /path/get
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 0 ]
+        - [ X-Request, first ]
+        - [ uuid, GET ]
+
+    proxy-request:
+      method: "GET"
+      headers:
+        fields:
+        - [ X-Request, { value: first, as: equal } ]
+        - [ X-Multiplexer, { value: copy, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, first ]
+
+    # There is no client since this response terminates at ATS, so no need for
+    # proxy-response.
+
+  - client-request:
+      method: "POST"
+      version: "1.1"
+      url: /path/post
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 8 ]
+        - [ X-Request, second ]
+        - [ uuid, POST ]
+
+    proxy-request:
+      method: "POST"
+      headers:
+        fields:
+        - [ X-Request, { value: second, as: equal } ]
+        - [ X-Multiplexer, { value: copy, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, second ]
+
+    # There is no client since this response terminates at ATS, so no need for
+    # proxy-response.
+
+  - client-request:
+      method: "PUT"
+      version: "1.1"
+      url: /path/put
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 8 ]
+        - [ X-Request, third ]
+        - [ uuid, PUT ]
+
+    proxy-request:
+      method: "PUT"
+      headers:
+        fields:
+        - [ X-Request, { value: third, as: equal } ]
+        - [ X-Multiplexer, { value: copy, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, third ]
+
+    # There is no client since this response terminates at ATS, so no need for
+    # proxy-response.
diff --git a/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy_skip_post.replay.yaml b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy_skip_post.replay.yaml
new file mode 100644
index 000000000..d655d488e
--- /dev/null
+++ b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_copy_skip_post.replay.yaml
@@ -0,0 +1,55 @@
+#  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.
+
+meta:
+  version: "1.0"
+
+sessions:
+- protocol:
+  - name: http
+  - name: tls
+  - name: tcp
+  - name: ip
+
+  transactions:
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      url: /path/get
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 0 ]
+        - [ X-Request, first ]
+        - [ uuid, GET ]
+
+    proxy-request:
+      method: "GET"
+      headers:
+        fields:
+        - [ X-Request, { value: first, as: equal } ]
+        - [ X-Multiplexer, { value: copy, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, first ]
+
+    # Since POST and POST requests are skipped, the multiplexed hosts should
+    # not receive them.
diff --git a/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original.replay.yaml b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original.replay.yaml
new file mode 100644
index 000000000..6db3db834
--- /dev/null
+++ b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original.replay.yaml
@@ -0,0 +1,122 @@
+#  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.
+
+meta:
+  version: "1.0"
+
+sessions:
+- protocol:
+  - name: http
+  - name: tls
+  - name: tcp
+  - name: ip
+
+  transactions:
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      url: /path/get
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 0 ]
+        - [ X-Request, first ]
+        - [ uuid, GET ]
+
+    proxy-request:
+      method: "GET"
+      headers:
+        fields:
+        - [ X-Request, { value: first, as: equal } ]
+        - [ X-Multiplexer, { value: original, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, first ]
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Response, { value: first, as: equal } ]
+
+  - client-request:
+      method: "POST"
+      version: "1.1"
+      url: /path/post
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 8 ]
+        - [ X-Request, second ]
+        - [ uuid, POST ]
+
+    proxy-request:
+      method: "POST"
+      headers:
+        fields:
+        - [ X-Request, { value: second, as: equal } ]
+        - [ X-Multiplexer, { value: original, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, second ]
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Response, { value: second, as: equal } ]
+
+  - client-request:
+      method: "PUT"
+      version: "1.1"
+      url: /path/put
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 8 ]
+        - [ X-Request, third ]
+        - [ uuid, PUT ]
+
+    proxy-request:
+      method: "PUT"
+      headers:
+        fields:
+        - [ X-Request, { value: third, as: equal } ]
+        - [ X-Multiplexer, { value: original, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, third ]
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Response, { value: third, as: equal } ]
diff --git a/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original_skip_post.replay.yaml b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original_skip_post.replay.yaml
new file mode 100644
index 000000000..4609a5f18
--- /dev/null
+++ b/tests/gold_tests/pluginTest/multiplexer/replays/multiplexer_original_skip_post.replay.yaml
@@ -0,0 +1,122 @@
+#  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.
+
+meta:
+  version: "1.0"
+
+sessions:
+- protocol:
+  - name: http
+  - name: tls
+  - name: tcp
+  - name: ip
+
+  transactions:
+  - client-request:
+      method: "GET"
+      version: "1.1"
+      url: /a/path
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 0 ]
+        - [ X-Request, first ]
+        - [ uuid, GET ]
+
+    proxy-request:
+      method: "GET"
+      headers:
+        fields:
+        - [ X-Request, { value: first, as: equal } ]
+        - [ X-Multiplexer, { value: original, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, first ]
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Response, { value: first, as: equal } ]
+
+  - client-request:
+      method: "POST"
+      version: "1.1"
+      url: /another/path
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 8 ]
+        - [ X-Request, second ]
+        - [ uuid, POST ]
+
+    # Since POST requests are "skipped", there will be no X-Multiplexer headers.
+    proxy-request:
+      method: "POST"
+      headers:
+        fields:
+        - [ X-Request, { value: second, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, second ]
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Response, { value: second, as: equal } ]
+
+  - client-request:
+      method: "PUT"
+      version: "1.1"
+      url: /path/put
+      headers:
+        fields:
+        - [ Host, origin.server.com ]
+        - [ Content-Length, 8 ]
+        - [ X-Request, third ]
+        - [ uuid, PUT ]
+
+    # Since POST requests are "skipped", there will be no X-Multiplexer headers.
+    proxy-request:
+      method: "PUT"
+      headers:
+        fields:
+        - [ X-Request, { value: third, as: equal } ]
+
+    server-response:
+      status: 200
+      reason: OK
+      headers:
+        fields:
+        - [ Content-Length, 32 ]
+        - [ X-Response, third ]
+
+    proxy-response:
+      status: 200
+      headers:
+        fields:
+        - [ X-Response, { value: third, as: equal } ]