From da4dc50153fe6cc7e562b63439dd8be4846e0dcf Mon Sep 17 00:00:00 2001
From: Alexander Stephan 
<alexander.step...@sap.com<mailto:alexander.step...@sap.com>>
Date: Fri, 15 Sep 2023 12:25:03 +0200
Subject: [PATCH 4/4] MEDIUM: tcp-act: Add new set-tlv TCP action for PPv2 TLVs

This commit adds an action called set-tlv(<id>) <expr> that allows to directly
update the TLV data structure within a connection for all type of connection
events. It can be used to modify TLVs before they are forwarded (if specified
in proxy-v2-options) while keeping the previously allocated memory, if the new
and the old value map to the same pool. This function can also be used to
enhance readability if setting many TLVs at once, as an alternative to 
specifying
type and value directly in the server.
---
 doc/configuration.txt                         |  25 +++-
 .../proxy_protocol_send_generic.vtc           |  31 +++++
 src/tcp_act.c                                 | 120 ++++++++++++++++--
 3 files changed, 161 insertions(+), 15 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index aeff9e4db..a0317f005 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -7011,6 +7011,7 @@ http-request <action> [options...] [ { if | unless } 
<condition> ]
     - set-src <expr>
     - set-src-port <expr>
     - set-timeout { server | tunnel } { <timeout> | <expr> }
+    - set-tlv(<id>) <expr>
     - set-tos <tos>
     - set-uri <fmt>
     - set-var(<var-name>[,<cond>...]) <expr>
@@ -7943,6 +7944,22 @@ http-request set-timeout { server | tunnel } { <timeout> 
| <expr> }
     http-request set-timeout tunnel 5s
     http-request set-timeout server req.hdr(host),map_int(host.lst)

+http-request set-tlv(<id>) <expr> [ { if | unless } <condition> ]
+
+  This is used to alter a PROXY protocol v2 TLV that has been sent by the 
client.
+  It can be used to efficiently alter already allocated TLVs in-place. If no 
TLV with
+  the specified TLV ID has been received yet, a new TLV with <id> and the 
current
+  value of <expr> is added.
+
+  The parameter <id> represents the 8 bit TLV type field in the range 0 to 255.
+  It can be expressed in decimal, hexadecimal format (prefixed by "0x") or 
octal
+  (prefixed by "0").
+
+  Typically, it is used together with generic proxy-v2-options.
+
+  Example:
+    http-request set-tlv(0xE1) str("foo")
+
 http-request set-tos <tos> [ { if | unless } <condition> ]

   This is used to set the TOS or DSCP field value of packets sent to the client
@@ -13502,6 +13519,7 @@ tcp-request content <action> [{if | unless} <condition>]
     - set-priority-offset <expr>
     - set-src <expr>
     - set-src-port <expr>
+    - set-tlv(<id>) <expr>
     - set-tos <tos>
     - set-var(<var-name>[,<cond>...]) <expr>
     - set-var-fmt(<var-name>[,<cond>...]) <fmt>
@@ -13741,6 +13759,11 @@ tcp-request content set-src-port <expr> [ { if | 
unless } <condition> ]
   specified expression. Please refer to "http-request set-src" and
   "http-request set-src-port" for a complete description.

+tcp-request content set-tlv(<id>) <expr> [ { if | unless } <condition> ]
+
+  This is used to alter a PROXY protocol v2 TLV that has been sent by the 
client.
+  Please refer to "http-request set-tlv" for a complete description.
+
 tcp-request content set-tos <tos> [ { if | unless } <condition> ]

   This is used to set the TOS or DSCP field value of packets sent to the client
@@ -16686,7 +16709,7 @@ proxy-v2-options <option>[,<option>]*
   or hexadecimal format (prefixed by "0x").

   Example 2:
-  server example_server 127.0.0.1:2319 send-proxy-v2 proxy-v2-options 
0xEE=%[str("foo")]
+  server example 127.0.0.1:2319 send-proxy-v2 proxy-v2-options 
0xEE=%[str("foo")]

   This will always send out the value "foo". Another common use case would be 
to
   reference a variable.
diff --git a/reg-tests/connection/proxy_protocol_send_generic.vtc 
b/reg-tests/connection/proxy_protocol_send_generic.vtc
index e0bd15a1b..1c48964be 100644
--- a/reg-tests/connection/proxy_protocol_send_generic.vtc
+++ b/reg-tests/connection/proxy_protocol_send_generic.vtc
@@ -24,6 +24,33 @@ haproxy h1 -conf {
         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)]

+        http-request set-tlv(0xE3) str("bar")
+        http-request set-var(txn.custom_tlv_c) fc_pp_tlv(0xE3)
+        http-after-response set-header proxy_custom_tlv_c 
%[var(txn.custom_tlv_c)]
+
+        # Check that we can alter the TLV in the connection on http-request 
level.
+        http-request set-tlv(0xE3) str("bar")
+        http-request set-var(txn.custom_tlv_c) fc_pp_tlv(0xE3)
+        http-after-response set-header proxy_custom_tlv_c 
%[var(txn.custom_tlv_c)]
+
+        # Check that we can alter the TLV in the connection on tcp-content 
level.
+        tcp-request content set-tlv(0xE4) str("bar")
+        http-request set-var(txn.custom_tlv_d) fc_pp_tlv(0xE4)
+        http-after-response set-header proxy_custom_tlv_d 
%[var(txn.custom_tlv_d)]
+
+        # Check that we can overwrite an existing TLV.
+        tcp-request content set-tlv(0xE5) str("bar")
+        http-request set-var(txn.custom_tlv_e) fc_pp_tlv(0xE5)
+        http-after-response set-header proxy_custom_tlv_e 
%[var(txn.custom_tlv_e)]
+
+        # Check that we can move from a small to a medium pool with length 129.
+        tcp-request content set-tlv(0xE6) str("                                
                                                                                
                 ")
+        http-request set-var(txn.custom_tlv_f) fc_pp_tlv(0xE6)
+        http-after-response set-header proxy_custom_tlv_f 
%[var(txn.custom_tlv_f),length]
+
+        # 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

@@ -32,4 +59,8 @@ client c1 -connect ${h1_feS_sock} {
     rxresp
     expect resp.http.proxy_custom_tlv_a == "foo"
     expect resp.http.proxy_custom_tlv_b == ""
+    expect resp.http.proxy_custom_tlv_c == "bar"
+    expect resp.http.proxy_custom_tlv_d == "bar"
+    expect resp.http.proxy_custom_tlv_e == "bar"
+    expect resp.http.proxy_custom_tlv_f == 129
 } -run
diff --git a/src/tcp_act.c b/src/tcp_act.c
index 291f70488..e2f2db70a 100644
--- a/src/tcp_act.c
+++ b/src/tcp_act.c
@@ -27,6 +27,7 @@
 #include <haproxy/action-t.h>
 #include <haproxy/api.h>
 #include <haproxy/arg.h>
+#include <haproxy/cfgparse.h>
 #include <haproxy/channel.h>
 #include <haproxy/connection.h>
 #include <haproxy/global.h>
@@ -406,6 +407,38 @@ static enum act_return tcp_action_set_tos(struct act_rule 
*rule, struct proxy *p
 }
 #endif

+/*
+ * Prepare the arguments for conn_set_tlv by converting the user input
+ */
+static enum act_return tcp_action_set_tlv(struct act_rule *rule, struct proxy 
*px,
+         struct session *sess, struct stream *s, int flags)
+{
+    struct sample *smp = NULL;
+
+    /* Fetch the argument value */
+    smp = sample_fetch_as_type(px, sess, s, 0, rule->arg.act.p[1], SMP_T_STR);
+
+    if (smp) {
+         /* Check whether the argument can be converted to a string */
+         if (sample_casts[smp->data.type][SMP_T_STR] &&
+                   sample_casts[smp->data.type][SMP_T_STR](smp)) {
+              struct ist value = ist2(smp->data.u.str.area, 
smp->data.u.str.data);
+
+              if (conn_set_tlv(objt_conn(sess->origin), 
(uintptr_t)rule->arg.act.p[0], value.ptr, value.len) < 0)
+                   return ACT_RET_ERR;
+         }
+    }
+    return ACT_RET_CONT;
+}
+
+/*
+ * Release the sample expr when releasing a set tlv action
+ */
+static void release_set_tlv_action(struct act_rule *rule)
+{
+    release_sample_expr(rule->arg.act.p[1]);
+}
+
 /*
  * Release the sample expr when releasing attach-srv action
  */
@@ -686,6 +719,63 @@ static enum act_parse_ret tcp_parse_silent_drop(const char 
**args, int *cur_arg,
     return ACT_RET_PRS_OK;
 }

+/* Parse a "set-tlv" action. It takes the TLV ID as argument which can be set 
to a
+ * sample expression. It returns ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on 
error.
+ */
+static enum act_parse_ret tcp_parse_set_tlv(const char **args, int *cur_arg, 
struct proxy *px,
+         struct act_rule *rule, char **err)
+{
+    const char *cmd_name = args[*cur_arg-1];
+    struct sample_expr *expr = NULL;
+
+    char *error = NULL;
+    unsigned int tlv_id = 0;
+
+    expr = sample_parse_expr((char **)args, cur_arg, px->conf.args.file, 
px->conf.args.line, err, &px->conf.args, NULL);
+    if (!expr || !expr->fetch->val)
+         return ACT_RET_PRS_ERR;
+
+    cmd_name += strlen("set-tlv");
+
+    if (*cmd_name == '(') {
+         cmd_name++; /* skip the '(' */
+         errno = 0;
+         tlv_id = strtoul(cmd_name, &error, 0); /* convert TLV ID */
+         if (errno == EINVAL) {
+              memprintf(err, "Invalid %s. Could not find a valid number", 
args[*cur_arg-1]);
+              return ACT_RET_PRS_ERR;
+         }
+         if (errno != 0) {
+              memprintf(err, "Invalid %s. Could not convert TLV ID", 
args[*cur_arg-1]);
+              return ACT_RET_PRS_ERR;
+         }
+         if (*error != ')') {
+              memprintf(err, "Invalid %s. Expects set-tlv(<TLV ID>)", 
args[*cur_arg-1]);
+              return ACT_RET_PRS_ERR;
+         }
+         if (tlv_id > 0xff) {
+              memprintf(err, "Invalid TLV ID '%s'. The maximum allowed TLV ID 
is %d.",
+                        args[*cur_arg-1], 0xff);
+              return ACT_RET_PRS_ERR;
+         }
+
+         /* prepare a custom action with the parsed sample expression and the 
TLV ID argument */
+         rule->arg.act.p[0] = (void *)(uintptr_t)tlv_id;
+         rule->arg.act.p[1] = (void *)(uintptr_t)expr;
+
+         rule->action = ACT_CUSTOM;
+
+         rule->action_ptr = tcp_action_set_tlv;
+         rule->release_ptr = release_set_tlv_action;
+    } else {
+         memprintf(err, "Invalid syntax '%s'. Expects set-tlv(<TLV ID>) 
<expr>", args[*cur_arg-1]);
+         return ACT_RET_PRS_ERR;
+    }
+
+    (*cur_arg)++;
+
+    return ACT_RET_PRS_OK;
+}

 static struct action_kw_list tcp_req_conn_actions = {ILH, {
     { "set-dst"     , tcp_parse_set_src_dst },
@@ -715,13 +805,14 @@ static struct action_kw_list tcp_req_sess_actions = {ILH, 
{
 INITCALL1(STG_REGISTER, tcp_req_sess_keywords_register, &tcp_req_sess_actions);

 static struct action_kw_list tcp_req_cont_actions = {ILH, {
-    { "set-src",      tcp_parse_set_src_dst },
-    { "set-src-port", tcp_parse_set_src_dst },
-    { "set-dst"     , tcp_parse_set_src_dst },
-    { "set-dst-port", tcp_parse_set_src_dst },
-    { "set-mark",     tcp_parse_set_mark    },
-    { "set-tos",      tcp_parse_set_tos     },
-    { "silent-drop",  tcp_parse_silent_drop },
+    { "set-src",      tcp_parse_set_src_dst               },
+    { "set-src-port", tcp_parse_set_src_dst               },
+    { "set-dst",      tcp_parse_set_src_dst               },
+    { "set-dst-port", tcp_parse_set_src_dst               },
+    { "set-mark",     tcp_parse_set_mark                  },
+    { "set-tlv",      tcp_parse_set_tlv, KWF_MATCH_PREFIX },
+    { "set-tos",      tcp_parse_set_tos                   },
+    { "silent-drop",  tcp_parse_silent_drop               },
     { /* END */ }
 }};

@@ -737,13 +828,14 @@ static struct action_kw_list tcp_res_cont_actions = {ILH, 
{
 INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_cont_actions);

 static struct action_kw_list http_req_actions = {ILH, {
-    { "set-dst",      tcp_parse_set_src_dst },
-    { "set-dst-port", tcp_parse_set_src_dst },
-    { "set-mark",     tcp_parse_set_mark    },
-    { "set-src",      tcp_parse_set_src_dst },
-    { "set-src-port", tcp_parse_set_src_dst },
-    { "set-tos",      tcp_parse_set_tos     },
-    { "silent-drop",  tcp_parse_silent_drop },
+    { "set-dst",      tcp_parse_set_src_dst               },
+    { "set-dst-port", tcp_parse_set_src_dst               },
+    { "set-mark",     tcp_parse_set_mark                  },
+    { "set-src",      tcp_parse_set_src_dst               },
+    { "set-src-port", tcp_parse_set_src_dst               },
+    { "set-tlv",      tcp_parse_set_tlv, KWF_MATCH_PREFIX },
+    { "set-tos",      tcp_parse_set_tos                   },
+    { "silent-drop",  tcp_parse_silent_drop               },
     { /* END */ }
 }};

--
2.35.3

Reply via email to