MEDIUM: tcp-act: <expr> support for set-fc-{mark,tos} actions

In this patch we add the possibility to use sample expression as argument
for set-fc-{mark,tos} actions. To make it backward compatible with
previous behavior, during parsing we first try to parse the value as
as integer (decimal or hex notation), and then fallback to expr parsing
in case of failure.

The documentation was updated accordingly.
This commit is contained in:
Aurelien DARRAGON 2024-01-18 17:17:41 +01:00
parent 03cb782bcb
commit b4ee7b044e
3 changed files with 134 additions and 42 deletions

View File

@ -14744,32 +14744,35 @@ set-dst-port <expr>
destination address to IPv4 "0.0.0.0" before rewriting the port.
set-fc-mark <mark>
set-fc-mark { <mark> | <expr> }
Usable in: TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
X | X | X | X | X | X | -
This is used to set the Netfilter/IPFW MARK on all packets sent to the client
to the value passed in <mark> on platforms which support it. This value is an
unsigned 32 bit value which can be matched by netfilter/ipfw and by the
routing table or monitoring the packets through DTrace. It can be expressed
both in decimal or hexadecimal format (prefixed by "0x").
This can be useful to force certain packets to take a different route (for
example a cheaper network path for bulk downloads). This works on Linux
kernels 2.6.32 and above and requires admin privileges, as well on FreeBSD
and OpenBSD.
to the value passed in <mark> or <expr> on platforms which support it. This
value is an unsigned 32 bit value which can be matched by netfilter/ipfw and
by the routing table or monitoring the packets through DTrace. <mark> can be
expressed both in decimal or hexadecimal format (prefixed by "0x").
Alternatively, <expr> can be used: it is a standard HAProxy expression formed
by a sample-fetch followed by some converters which must resolve to integer
type. This action can be useful to force certain packets to take a different
route (for example a cheaper network path for bulk downloads). This works on
Linux kernels 2.6.32 and above and requires admin privileges, as well on
FreeBSD and OpenBSD.
set-fc-tos <tos>
set-fc-tos { <tos | <expr> }
Usable in: TCP RqCon| RqSes| RqCnt| RsCnt| HTTP Req| Res| Aft
X | X | X | X | X | X | -
This is used to set the TOS or DSCP field value of packets sent to the client
to the value passed in <tos> on platforms which support this. This value
represents the whole 8 bits of the IP TOS field, and can be expressed both in
decimal or hexadecimal format (prefixed by "0x"). Note that only the 6 higher
bits are used in DSCP or TOS, and the two lower bits are always 0. This can
be used to adjust some routing behavior on border routers based on some
information from the request.
to the value passed in <tos> or <expr> on platforms which support this. This
value represents the whole 8 bits of the IP TOS field. Note that only the 6
higher bits are used in DSCP or TOS, and the two lower bits are always 0.
Alternatively, <expr> can be used: it is a standard HAProxy expression formed
by a sample-fetch followed by some converters which must resolve to integer
type. This action can be used to adjust some routing behavior on border
routers based on some information from the request.
See RFC 2474, 2597, 3260 and 4594 for more information.

View File

@ -191,6 +191,10 @@ struct act_rule {
struct server *srv; /* target server to attach the connection */
struct sample_expr *name; /* used to differentiate idle connections */
} attach_srv; /* 'attach-srv' rule */
struct {
int value;
struct sample_expr *expr;
} expr_int; /* expr or int value (when expr is NULL)*/
struct {
void *p[4];
} act; /* generic pointers to be used by custom actions */

View File

@ -71,6 +71,29 @@ static enum act_return tcp_action_attach_srv(struct act_rule *rule, struct proxy
return ACT_RET_CONT;
}
/* tries to extract integer value from rule's argument:
* if expr is set, computes expr and sets the result into <value>
* else, it's already a numerical value, use it as-is.
*
* Returns 1 on success and 0 on failure.
*/
static int extract_int_from_rule(struct act_rule *rule,
struct proxy *px, struct session *sess, struct stream *s,
int *value)
{
struct sample *smp;
if (!rule->arg.expr_int.expr) {
*value = rule->arg.expr_int.value;
return 1;
}
smp = sample_fetch_as_type(px, sess, s, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.expr_int.expr, SMP_T_SINT);
if (!smp)
return 0;
*value = smp->data.u.sint;
return 1;
}
/*
* Execute the "set-src" action. May be called from {tcp,http}request.
* It only changes the address and tries to preserve the original port. If the
@ -392,7 +415,10 @@ static enum act_return tcp_exec_action_silent_drop(struct act_rule *rule, struct
static enum act_return tcp_action_set_fc_mark(struct act_rule *rule, struct proxy *px,
struct session *sess, struct stream *s, int flags)
{
conn_set_mark(objt_conn(sess->origin), (uintptr_t)rule->arg.act.p[0]);
unsigned int mark;
if (extract_int_from_rule(rule, px, sess, s, (int *)&mark))
conn_set_mark(objt_conn(sess->origin), mark);
return ACT_RET_CONT;
}
#endif
@ -401,7 +427,10 @@ static enum act_return tcp_action_set_fc_mark(struct act_rule *rule, struct prox
static enum act_return tcp_action_set_fc_tos(struct act_rule *rule, struct proxy *px,
struct session *sess, struct stream *s, int flags)
{
conn_set_tos(objt_conn(sess->origin), (uintptr_t)rule->arg.act.p[0]);
int tos;
if (extract_int_from_rule(rule, px, sess, s, &tos))
conn_set_tos(objt_conn(sess->origin), tos);
return ACT_RET_CONT;
}
#endif
@ -423,6 +452,14 @@ static void release_set_src_dst_action(struct act_rule *rule)
release_sample_expr(rule->arg.expr);
}
/*
* Release expr_int rule argument when action is no longer used
*/
static __maybe_unused void release_expr_int_action(struct act_rule *rule)
{
release_sample_expr(rule->arg.expr_int.expr);
}
static int tcp_check_attach_srv(struct act_rule *rule, struct proxy *px, char **err)
{
struct proxy *be = NULL;
@ -565,29 +602,53 @@ static enum act_parse_ret tcp_parse_set_src_dst(const char **args, int *orig_arg
/* Parse a "set-mark" action. It takes the MARK value as argument. It returns
* ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
*/
static enum act_parse_ret tcp_parse_set_mark(const char **args, int *cur_arg, struct proxy *px,
struct act_rule *rule, char **err)
static enum act_parse_ret tcp_parse_set_mark(const char **args, int *orig_arg, struct proxy *px,
struct act_rule *rule, char **err)
{
#if defined(SO_MARK) || defined(SO_USER_COOKIE) || defined(SO_RTABLE)
struct sample_expr *expr;
char *endp;
unsigned int mark;
unsigned int where;
int cur_arg = *orig_arg;
if (!*args[*cur_arg]) {
memprintf(err, "expects exactly 1 argument (integer/hex value)");
return ACT_RET_PRS_ERR;
}
mark = strtoul(args[*cur_arg], &endp, 0);
if (endp && *endp != '\0') {
memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
if (!*args[*orig_arg]) {
memprintf(err, "expects an argument");
return ACT_RET_PRS_ERR;
}
(*cur_arg)++;
/* value may be either an unsigned integer or an expression */
rule->arg.expr_int.expr = NULL;
rule->arg.expr_int.value = strtoul(args[*orig_arg], &endp, 0);
if (*endp == '\0') {
/* valid unsigned integer */
(*orig_arg)++;
}
else {
/* invalid unsigned integer, fallback to expr */
expr = sample_parse_expr((char **)args, orig_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
if (!expr)
return ACT_RET_PRS_ERR;
where = 0;
if (px->cap & PR_CAP_FE)
where |= SMP_VAL_FE_HRQ_HDR;
if (px->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRQ_HDR;
if (!(expr->fetch->val & where)) {
memprintf(err,
"fetch method '%s' extracts information from '%s', none of which is available here",
args[cur_arg-1], sample_src_names(expr->fetch->use));
free(expr);
return ACT_RET_PRS_ERR;
}
rule->arg.expr_int.expr = expr;
}
/* Register processing function. */
rule->action_ptr = tcp_action_set_fc_mark;
rule->action = ACT_CUSTOM;
rule->arg.act.p[0] = (void *)(uintptr_t)mark;
rule->release_ptr = release_expr_int_action;
global.last_checks |= LSTCHK_NETADM;
return ACT_RET_PRS_OK;
#else
@ -600,29 +661,53 @@ static enum act_parse_ret tcp_parse_set_mark(const char **args, int *cur_arg, st
/* Parse a "set-tos" action. It takes the TOS value as argument. It returns
* ACT_RET_PRS_OK on success, ACT_RET_PRS_ERR on error.
*/
static enum act_parse_ret tcp_parse_set_tos(const char **args, int *cur_arg, struct proxy *px,
struct act_rule *rule, char **err)
static enum act_parse_ret tcp_parse_set_tos(const char **args, int *orig_arg, struct proxy *px,
struct act_rule *rule, char **err)
{
#ifdef IP_TOS
struct sample_expr *expr;
char *endp;
int tos;
unsigned int where;
int cur_arg = *orig_arg;
if (!*args[*cur_arg]) {
memprintf(err, "expects exactly 1 argument (integer/hex value)");
return ACT_RET_PRS_ERR;
}
tos = strtol(args[*cur_arg], &endp, 0);
if (endp && *endp != '\0') {
memprintf(err, "invalid character starting at '%s' (integer/hex value expected)", endp);
if (!*args[*orig_arg]) {
memprintf(err, "expects an argument");
return ACT_RET_PRS_ERR;
}
(*cur_arg)++;
/* value may be either an integer or an expression */
rule->arg.expr_int.expr = NULL;
rule->arg.expr_int.value = strtol(args[*orig_arg], &endp, 0);
if (*endp == '\0') {
/* valid integer */
(*orig_arg)++;
}
else {
/* invalid unsigned integer, fallback to expr */
expr = sample_parse_expr((char **)args, orig_arg, px->conf.args.file, px->conf.args.line, err, &px->conf.args, NULL);
if (!expr)
return ACT_RET_PRS_ERR;
where = 0;
if (px->cap & PR_CAP_FE)
where |= SMP_VAL_FE_HRQ_HDR;
if (px->cap & PR_CAP_BE)
where |= SMP_VAL_BE_HRQ_HDR;
if (!(expr->fetch->val & where)) {
memprintf(err,
"fetch method '%s' extracts information from '%s', none of which is available here",
args[cur_arg-1], sample_src_names(expr->fetch->use));
free(expr);
return ACT_RET_PRS_ERR;
}
rule->arg.expr_int.expr = expr;
}
/* Register processing function. */
rule->action_ptr = tcp_action_set_fc_tos;
rule->action = ACT_CUSTOM;
rule->arg.act.p[0] = (void *)(uintptr_t)tos;
rule->release_ptr = release_expr_int_action;
return ACT_RET_PRS_OK;
#else
memprintf(err, "not supported on this platform (IP_TOS undefined)");