From ca58a665ce1b594912c608c4474b2fbddad01de4 Mon Sep 17 00:00:00 2001
From: Alexander Stephan <alexander.stephan@sap.com>
Date: Wed, 16 Aug 2023 15:53:51 +0200
Subject: [PATCH 3/6] MEDIUM: sample: Add fetch for arbitrary TLVs

Based on the new, generic allocation infrastructure, a new sample
fetch fc_pp_tlv is introduced. It is an abstraction for existing
PPv2 TLV sample fetches. It takes any valid TLV ID as argument and
returns the value as a string, similar to fc_pp_authority and
fc_pp_unique_id.
---
 doc/configuration.txt             |  4 ++
 reg-tests/sample_fetches/tlvs.vtc | 57 ++++++++++++++++++++++++
 src/connection.c                  | 72 ++++++++++++++++++++++++++++++-
 3 files changed, 131 insertions(+), 2 deletions(-)
 create mode 100644 reg-tests/sample_fetches/tlvs.vtc

diff --git a/doc/configuration.txt b/doc/configuration.txt
index bb47a3f98..7986398b6 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -20131,6 +20131,10 @@ fc_pp_unique_id : string
   Returns the unique ID TLV sent by the client in the PROXY protocol header,
   if any.
 
+fc_pp_tlv(<id>) : string
+  Returns the TLV value for the given ID sent by the client in the PROXY
+  protocol header, if any.
+
 fc_rcvd_proxy : boolean
   Returns true if the client initiated the connection with a PROXY protocol
   header.
diff --git a/reg-tests/sample_fetches/tlvs.vtc b/reg-tests/sample_fetches/tlvs.vtc
new file mode 100644
index 000000000..9312b1df9
--- /dev/null
+++ b/reg-tests/sample_fetches/tlvs.vtc
@@ -0,0 +1,57 @@
+varnishtest "Tests for fetching PROXY protocol v2 TLVs"
+feature ignore_unknown_macro
+
+haproxy h1 -conf {
+    defaults
+        mode http
+        timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout client  "${HAPROXY_TEST_TIMEOUT-5s}"
+        timeout server  "${HAPROXY_TEST_TIMEOUT-5s}"
+
+    frontend echo
+        bind "fd@${fe1}" accept-proxy
+        tcp-request content set-var(sess.aws) fc_pp_tlv(0xEA),bytes(1) if { fc_pp_tlv(0xEE),bytes(0,1),hex eq 01 }
+        tcp-request content set-var(sess.azure) fc_pp_tlv(0xEE),bytes(1) if { fc_pp_tlv(0xEA),bytes(0,1),hex eq 01 }
+
+        http-after-response set-header echo1 %[var(sess.aws)]
+        http-after-response set-header echo2 %[var(sess.azure)]
+        http-after-response set-header echo3 %[fc_pp_tlv(0xEB)]
+        http-after-response set-header echo4 %[fc_pp_tlv(0xEC),length]
+        http-request return status 200
+} -start
+
+client c1 -connect ${h1_fe1_sock} {
+    # PROXY v2 signature
+    sendhex "0d 0a 0d 0a 00 0d 0a 51 55 49 54 0a"
+    # version + PROXY
+    sendhex "21"
+    # TCP4
+    sendhex "11"
+    # length of the address (12) + length of the TLVs (14 + 10 + 9 + 131)
+    sendhex "00 B0"
+    # 127.0.0.1 42 127.0.0.1 1337
+    sendhex "7F 00 00 01 7F 00 00 01 00 2A 05 39"
+
+    # PP2_TYPE_AWS (0xEA) + length of the value + PP2_SUBTYPE_AWS_VPCE_ID (0x01) + "aws-vpc-id"
+    # See https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#custom-tlv for the respective definitions.
+    sendhex "EA 00 0B 01 61 77 73 2D 76 70 63 2D 69 64"
+
+    # PP2_TYPE_AZURE (0xEE) + length of the value + PP2_SUBTYPE_AZURE_PRIVATEENDPOINT_LINKID (0x01) + "LINKID"
+    # See https://learn.microsoft.com/en-us/azure/private-link/private-link-service-overview#getting-connection-information-using-tcp-proxy-v2
+    # for the respective definitions.
+    sendhex "EE 00 07 01 4C 49 4E 4B 49 44"
+
+    # custom type (0xEB) + length of the value + "custom"
+    sendhex "EB 00 06 63 75 73 74 6F 6D"
+
+    # custom type (0xEC) + length of the value (128, does not fit in pool) + random data
+    sendhex "EC 00 80 3A D9 32 9B 11 A7 29 81 14 B2 33 F0 C2 0D 7A 53 D1 97 28 74 4B 78 8A D3 10 C4 B1 88 42 9C 63 8E 8B 8A A0 B4 B0 E7 9D 20 27 0F 1E 53 4D 33 F7 5A D0 91 3F B8 C9 E9 16 C4 61 C5 13 02 92 64 9D D4 22 5C 8E 4E 0B 2D 2D 7D 9F 5D 97 9B 25 C4 12 7D 21 75 C8 15 92 6B 64 F2 5F C0 A9 0F 9A 7D 0A 6D 68 79 F4 56 18 6F 23 45 2A 9B 36 34 3A 47 43 32 29 18 6F 23 45 2A 9B 36 34 3A 47 43 32 29 32 29"
+
+    txreq -url "/"
+    rxresp
+    expect resp.status == 200
+    expect resp.http.echo1 == "aws-vpc-id"
+    expect resp.http.echo2 == "LINKID"
+    expect resp.http.echo3 == "custom"
+    expect resp.http.echo4 == 128
+} -run
diff --git a/src/connection.c b/src/connection.c
index d9daed37c..2c00f31d3 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -15,6 +15,7 @@
 #include <import/ebmbtree.h>
 
 #include <haproxy/api.h>
+#include <haproxy/arg.h>
 #include <haproxy/cfgparse.h>
 #include <haproxy/connection.h>
 #include <haproxy/fd.h>
@@ -2198,11 +2199,77 @@ int smp_fetch_fc_rcvd_proxy(const struct arg *args, struct sample *smp, const ch
 	return 1;
 }
 
+/*
+ * This function checks the TLV type converter configuration.
+ * It expects the corresponding TLV type as a string representing the number.
+ * args[0] will be turned into the numerical value of the TLV type string.
+ */
+static int smp_check_tlv_type(struct arg *args, char **err)
+{
+	int type;
+	char *endp;
+
+	type = strtoul(args[0].data.str.area, &endp, 0);
+	if (endp && *endp != '\0') {
+		memprintf(err, "Could not convert type '%s'", args[0].data.str.area);
+		return 0;
+	}
+
+	if (type < 0 || type > 255) {
+		memprintf(err, "Invalid TLV Type '%s'", args[0].data.str.area);
+		return 0;
+	}
+
+	chunk_destroy(&args[0].data.str);
+	args[0].type = ARGT_SINT;
+	args[0].data.sint = type;
+
+	return 1;
+}
+
+/* fetch an arbitrary TLV from a PROXY protocol v2 header */
+int smp_fetch_fc_pp_tlv(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+	int idx;
+	struct connection *conn = NULL;
+	struct conn_tlv_list *conn_tlv = NULL;
+
+	conn = objt_conn(smp->sess->origin);
+	if (!conn)
+		return 0;
+
+	if (conn->flags & CO_FL_WAIT_XPRT) {
+		smp->flags |= SMP_F_MAY_CHANGE;
+		return 0;
+	}
+
+	if (args[0].type != ARGT_SINT)
+		return 0;
+
+	idx = args[0].data.sint;
+	conn_tlv = smp->ctx.p ? smp->ctx.p : LIST_ELEM(conn->tlv_list.n, struct conn_tlv_list *, list);
+	list_for_each_entry_from(conn_tlv, &conn->tlv_list, list) {
+		if (conn_tlv->type == idx) {
+			smp->flags |= SMP_F_NOT_LAST;
+			smp->data.type = SMP_T_STR;
+			smp->data.u.str.area = conn_tlv->value;
+			smp->data.u.str.data = conn_tlv->len;
+			smp->ctx.p = conn_tlv;
+
+			return 1;
+		}
+	}
+
+	smp->flags &= ~SMP_F_NOT_LAST;
+
+	return 0;
+}
+
 /* fetch the authority TLV from a PROXY protocol header */
 int smp_fetch_fc_pp_authority(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
 	struct connection *conn = NULL;
-	struct conn_tlv_list *conn_tlv;
+	struct conn_tlv_list *conn_tlv = NULL;
 
 	conn = objt_conn(smp->sess->origin);
 	if (!conn)
@@ -2235,7 +2302,7 @@ int smp_fetch_fc_pp_authority(const struct arg *args, struct sample *smp, const
 int smp_fetch_fc_pp_unique_id(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
 	struct connection *conn = NULL;
-	struct conn_tlv_list *conn_tlv;
+	struct conn_tlv_list *conn_tlv = NULL;
 
 	conn = objt_conn(smp->sess->origin);
 	if (!conn)
@@ -2338,6 +2405,7 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
 	{ "fc_rcvd_proxy", smp_fetch_fc_rcvd_proxy, 0, NULL, SMP_T_BOOL, SMP_USE_L4CLI },
 	{ "fc_pp_authority", smp_fetch_fc_pp_authority, 0, NULL, SMP_T_STR, SMP_USE_L4CLI },
 	{ "fc_pp_unique_id", smp_fetch_fc_pp_unique_id, 0, NULL, SMP_T_STR, SMP_USE_L4CLI },
+	{ "fc_pp_tlv", smp_fetch_fc_pp_tlv, ARG1(1, STR), smp_check_tlv_type, SMP_T_STR, SMP_USE_L4CLI },
 	{ /* END */ },
 }};
 
-- 
2.35.3

