From 0f8691072ef17ceac98c6fffb063fdacc016a99c Mon Sep 17 00:00:00 2001
From: Alexander Stephan <alexander.stephan@sap.com>
Date: Sat, 28 Oct 2023 20:57:07 +0200
Subject: [PATCH 2/2] MINOR: connection: Send out generic, user-defined server
 TLVs

To follow-up the implementation of the new set-proxy-v2-tlv-fmt
keyword in the server, the connection is updated to use the previously
allocated TLVs. If no value was specified, we send out an empty TLV.
As the feature is fully working with this commit, documentation and a
test for the server and default-server are added as well.
---
 doc/configuration.txt                         | 16 ++++
 .../proxy_protocol_send_generic.vtc           | 74 +++++++++++++++++++
 src/connection.c                              | 36 ++++++++-
 3 files changed, 124 insertions(+), 2 deletions(-)
 create mode 100644 reg-tests/connection/proxy_protocol_send_generic.vtc

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 3dcdb3e2a..b54e9cd2a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -16725,6 +16725,22 @@ send-proxy-v2
   of this version of the protocol. See also the "no-send-proxy-v2" option of
   this section and send-proxy" option of the "bind" keyword.
 
+set-proxy-v2-tlv-fmt(<id>) <fmt>
+  The "set-proxy-v2-tlv-fmt" parameter is used to send arbitrary PROXY protocol
+  version 2 TLVs. For the type (<id>) range of the defined TLV type please refer
+  to section 2.2.8. of the proxy protocol specification. However, the value can
+  be chosen freely as long as it does not exceed the maximum length of 65,535
+  bytes. It can also be used for forwarding TLVs by using the fetch "fc_pp_tlv"
+  to retrieve a received TLV from the frontend. It may be used as a server or
+  a default-server option. It must be used in combination with send-proxy-v2
+  such that PPv2 TLVs are actually sent out.
+
+  Example:
+  server srv1 192.168.1.1:80 send-proxy-v2 set-proxy-v2-tlv-fmt(0x20) %[fc_pp_tlv(0x20)]
+
+  In this case, we fetch the TLV with the type 0x20 as a string and set as the value
+  of a newly created TLV that also has the type 0x20.
+
 proxy-v2-options <option>[,<option>]*
   The "proxy-v2-options" parameter add options to send in PROXY protocol
   version 2 when "send-proxy-v2" is used. Options available are:
diff --git a/reg-tests/connection/proxy_protocol_send_generic.vtc b/reg-tests/connection/proxy_protocol_send_generic.vtc
new file mode 100644
index 000000000..f61bcae73
--- /dev/null
+++ b/reg-tests/connection/proxy_protocol_send_generic.vtc
@@ -0,0 +1,74 @@
+varnishtest "Check that generic TLV IDs are sent properly"
+
+#REQUIRE_VERSION=2.2
+
+feature ignore_unknown_macro
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        log global
+
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    listen sender
+        bind "fd@${feS}"
+        server example ${h1_feR_addr}:${h1_feR_port} send-proxy-v2 set-proxy-v2-tlv-fmt(0xE1) %[str("foo")] set-proxy-v2-tlv-fmt(0xE2)
+
+    listen receiver
+        bind "fd@${feR}" accept-proxy
+
+        # Check that the TLV value is set in the backend.
+        http-request set-var(txn.custom_tlv_a) fc_pp_tlv(0xE1)
+        http-after-response set-header proxy_custom_tlv_a %[var(txn.custom_tlv_a)]
+
+        # Check that TLVs without an value are sent out.
+        http-request set-var(txn.custom_tlv_b) fc_pp_tlv(0xE2)
+        http-after-response set-header proxy_custom_tlv_b %[var(txn.custom_tlv_b)]
+
+        # Note that we do not check for an invalid TLV ID as that would result in an
+        # parser error anway.
+
+        http-request return status 200
+} -start
+
+
+client c1 -connect ${h1_feS_sock} {
+    txreq -url "/"
+    rxresp
+    expect resp.http.proxy_custom_tlv_a == "foo"
+    expect resp.http.proxy_custom_tlv_b == ""
+} -run
+
+# Ensure that we achieve the same via a default-server.
+haproxy h2 -conf {
+    defaults
+        mode http
+        log global
+
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    listen sender
+        bind "fd@${feS}"
+        default-server send-proxy-v2 set-proxy-v2-tlv-fmt(0xE1) %[str("bar")]
+        server example ${h1_feR_addr}:${h1_feR_port}
+
+    listen receiver
+        bind "fd@${feR}" accept-proxy
+
+        http-request set-var(txn.custom_tlv_a) fc_pp_tlv(0xE1)
+        http-after-response set-header proxy_custom_tlv_a %[var(txn.custom_tlv_a)]
+
+        http-request return status 200
+} -start
+
+
+client c2 -connect ${h2_feS_sock} {
+    txreq -url "/"
+    rxresp
+    expect resp.http.proxy_custom_tlv_a == "bar"
+} -run
diff --git a/src/connection.c b/src/connection.c
index 59893fbb5..3a13c2542 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -1927,10 +1927,11 @@ static int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct
 	int ret = 0;
 	struct proxy_hdr_v2 *hdr = (struct proxy_hdr_v2 *)buf;
 	struct sockaddr_storage null_addr = { .ss_family = 0 };
+	struct srv_pp_tlv_list *srv_tlv = NULL;
 	const struct sockaddr_storage *src = &null_addr;
 	const struct sockaddr_storage *dst = &null_addr;
-	const char *value;
-	int value_len;
+	const char *value = "";
+	int value_len = 0;
 
 	if (buf_len < PP2_HEADER_LEN)
 		return 0;
@@ -2000,6 +2001,37 @@ static int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct
 		}
 	}
 
+	if (strm) {
+		struct buffer *replace = NULL;
+
+		list_for_each_entry(srv_tlv, &srv->pp_tlvs, list) {
+			replace = NULL;
+
+			/* Users will always need to provide a value, in case of forwarding, they should use fc_pp_tlv.
+			 * for generic types. Otherwise, we will send an empty TLV.
+			 */
+			if (!LIST_ISEMPTY(&srv_tlv->fmt)) {
+				replace = alloc_trash_chunk();
+				if (unlikely(!replace))
+					return 0;
+
+				replace->data = build_logline(strm, replace->area, replace->size, &srv_tlv->fmt);
+
+				if (unlikely((buf_len - ret) < sizeof(struct tlv))) {
+					free_trash_chunk(replace);
+					return 0;
+				}
+				ret += make_tlv(&buf[ret], (buf_len - ret), srv_tlv->type, replace->data, replace->area);
+				free_trash_chunk(replace);
+			}
+			else {
+				/* Create empty TLV as no value was specified */
+				ret += make_tlv(&buf[ret], (buf_len - ret), srv_tlv->type, 0, NULL);
+			}
+		}
+	}
+
+	/* Handle predefined TLVs as usual */
 	if (srv->pp_opts & SRV_PP_V2_CRC32C) {
 		uint32_t zero_crc32c = 0;
 
-- 
2.35.3

