diff --git a/doc/configuration.txt b/doc/configuration.txt index 81a90daf9..d4bfd1c65 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -6265,6 +6265,24 @@ http-request set-src-port [ { if | unless } ] the address family supports a port, otherwise it forces the source address to IPv4 "0.0.0.0" before rewriting the port. +http-request set-timeout server|tunnel { | } + [ { if | unless } ] + + This action overrides the specified "server" or "tunnel" timeout for the + current stream only. The timeout can be specified in millisecond or with any + other unit if the number is suffixed by the unit as explained at the top of + this document. It is also possible to write an expression which must returns + a number interpreted as a timeout in millisecond. + + Note that the server/tunnel timeouts are only relevant on the backend side + and thus this rule is only available for the proxies with backend + capabilities. Also the timeout value must be non-null to obtain the expected + results. + + Example: + http-request set-timeout server 5s + http-request set-timeout hdr(host),map_int(host.lst) + http-request set-tos [ { if | unless } ] This is used to set the TOS or DSCP field value of packets sent to the client diff --git a/include/haproxy/action-t.h b/include/haproxy/action-t.h index 73a846f1c..2ea524db2 100644 --- a/include/haproxy/action-t.h +++ b/include/haproxy/action-t.h @@ -143,6 +143,11 @@ struct act_rule { struct sample_expr *expr; int idx; } capid; + struct { + int value; /* plain timeout value in ms if no expr is used */ + enum act_timeout_name type; /* timeout type */ + struct sample_expr *expr; /* timeout value as an expression */ + } timeout; struct hlua_rule *hlua_rule; struct { struct sample_expr *expr; diff --git a/include/haproxy/action.h b/include/haproxy/action.h index c7ce92dc5..c5c0a6c4b 100644 --- a/include/haproxy/action.h +++ b/include/haproxy/action.h @@ -25,6 +25,7 @@ #include #include #include +#include int act_resolution_cb(struct dns_requester *requester, struct dns_nameserver *nameserver); int act_resolution_error_cb(struct dns_requester *requester, int error_code); @@ -90,4 +91,14 @@ int check_trk_action(struct act_rule *rule, struct proxy *px, char **err); */ int check_capture(struct act_rule *rule, struct proxy *px, char **err); +int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout, + enum act_timeout_name *name, + struct sample_expr **expr, char **err, + const char *file, int line, struct arg_list *al); + +static inline void release_timeout_action(struct act_rule *rule) +{ + release_sample_expr(rule->arg.timeout.expr); +} + #endif /* _HAPROXY_ACTION_H */ diff --git a/src/action.c b/src/action.c index 870a8393a..c4c263faf 100644 --- a/src/action.c +++ b/src/action.c @@ -149,3 +149,60 @@ int act_resolution_error_cb(struct dns_requester *requester, int error_code) return 0; } +/* Parse a set-timeout rule statement. It first checks if the timeout name is + * valid and returns it in . Then the timeout is parsed as a plain value + * and * returned in . If there is a parsing error, the value is + * reparsed as an expression and returned in . + * + * Returns -1 if the name is invalid or neither a time or an expression can be + * parsed, or if the timeout value is 0. + */ +int cfg_parse_rule_set_timeout(const char **args, int idx, int *out_timeout, + enum act_timeout_name *name, + struct sample_expr **expr, char **err, + const char *file, int line, struct arg_list *al) +{ + const char *res; + const char *timeout_name = args[idx++]; + + if (!strcmp(timeout_name, "server")) { + *name = ACT_TIMEOUT_SERVER; + } + else if (!strcmp(timeout_name, "tunnel")) { + *name = ACT_TIMEOUT_TUNNEL; + } + else { + memprintf(err, + "'set-timeout' rule supports 'server'/'tunnel' (got '%s')", + timeout_name); + return -1; + } + + res = parse_time_err(args[idx], (unsigned int *)out_timeout, TIME_UNIT_MS); + if (res == PARSE_TIME_OVER) { + memprintf(err, "timer overflow in argument '%s' to rule 'set-timeout %s' (maximum value is 2147483647 ms or ~24.8 days)", + args[idx], timeout_name); + return -1; + } + else if (res == PARSE_TIME_UNDER) { + memprintf(err, "timer underflow in argument '%s' to rule 'set-timeout %s' (minimum value is 1 ms)", + args[idx], timeout_name); + return -1; + } + /* res not NULL, parsing error */ + else if (res) { + *expr = sample_parse_expr((char **)args, &idx, file, line, err, al, NULL); + if (!*expr) { + memprintf(err, "unexpected character '%c' in rule 'set-timeout %s'", *res, timeout_name); + return -1; + } + } + /* res NULL, parsing ok but value is 0 */ + else if (!(*out_timeout)) { + memprintf(err, "null value is not valid for a 'set-timeout %s' rule", + timeout_name); + return -1; + } + + return 0; +} diff --git a/src/http_act.c b/src/http_act.c index 85a534e8d..140cdf1a9 100644 --- a/src/http_act.c +++ b/src/http_act.c @@ -1901,6 +1901,67 @@ static enum act_parse_ret parse_http_track_sc(const char **args, int *orig_arg, return ACT_RET_PRS_OK; } +static enum act_return action_timeout_set_stream_timeout(struct act_rule *rule, + struct proxy *px, + struct session *sess, + struct stream *s, + int flags) +{ + struct sample *key; + + if (rule->arg.timeout.expr) { + key = sample_fetch_as_type(px, sess, s, SMP_OPT_FINAL, rule->arg.timeout.expr, SMP_T_SINT); + if (!key) + return ACT_RET_CONT; + + stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(key->data.u.sint)); + } + else { + stream_set_timeout(s, rule->arg.timeout.type, MS_TO_TICKS(rule->arg.timeout.value)); + } + + return ACT_RET_CONT; +} + +/* Parse a "set-timeout" action. Returns ACT_RET_PRS_ERR if parsing error. + */ +static enum act_parse_ret parse_http_set_timeout(const char **args, + int *orig_arg, + struct proxy *px, + struct act_rule *rule, char **err) +{ + int cur_arg; + + rule->action = ACT_CUSTOM; + rule->action_ptr = action_timeout_set_stream_timeout; + rule->release_ptr = release_timeout_action; + + cur_arg = *orig_arg; + if (!*args[cur_arg] || !*args[cur_arg + 1]) { + memprintf(err, "expects exactly 2 arguments"); + return ACT_RET_PRS_ERR; + } + + if (!(px->cap & PR_CAP_BE)) { + memprintf(err, "proxy '%s' has no backend capability", px->id); + return ACT_RET_PRS_ERR; + } + + if (cfg_parse_rule_set_timeout(args, cur_arg, + &rule->arg.timeout.value, + &rule->arg.timeout.type, + &rule->arg.timeout.expr, + err, + px->conf.args.file, + px->conf.args.line, &px->conf.args) == -1) { + return ACT_RET_PRS_ERR; + } + + *orig_arg = cur_arg + 2; + + return ACT_RET_PRS_OK; +} + /* This function executes a strict-mode actions. On success, it always returns * ACT_RET_CONT */ @@ -2034,6 +2095,7 @@ static struct action_kw_list http_req_actions = { { "strict-mode", parse_http_strict_mode, 0 }, { "tarpit", parse_http_deny, 0 }, { "track-sc", parse_http_track_sc, 1 }, + { "set-timeout", parse_http_set_timeout, 0 }, { NULL, NULL } } };