diff --git a/doc/configuration.txt b/doc/configuration.txt index ed9fb4d48..3cb7e1a8b 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -14744,32 +14744,35 @@ set-dst-port destination address to IPv4 "0.0.0.0" before rewriting the port. -set-fc-mark +set-fc-mark { | } 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 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 or 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. can be + expressed both in decimal or hexadecimal format (prefixed by "0x"). + Alternatively, 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 +set-fc-tos { } 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 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 or 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, 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. diff --git a/include/haproxy/action-t.h b/include/haproxy/action-t.h index f77bdce5f..e5720428f 100644 --- a/include/haproxy/action-t.h +++ b/include/haproxy/action-t.h @@ -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 */ diff --git a/src/tcp_act.c b/src/tcp_act.c index 353849cb3..3da44ff08 100644 --- a/src/tcp_act.c +++ b/src/tcp_act.c @@ -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 + * 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)");