MEDIUM: Add tcp-request switch-mode action to perform HTTP upgrade
It is now possible to perform HTTP upgrades on a TCP stream from the frontend side. To do so, a tcp-request content rule must be defined with the switch-mode action, specifying the mode (for now, only http is supported) and optionnaly the proto (h1 or h2). This way it could be possible to set HTTP directives on a TCP frontend which will only be evaluated if an upgrade is performed. This new way to perform HTTP upgrades should replace progressively the old way, consisting to route the request to an HTTP backend. And it should be also a good start to remove all HTTP processing from tcp-request content rules. This action is terminal, it stops the ruleset evaluation. It is only available on proxy with the frontend capability. The configuration manual has been updated accordingly.
This commit is contained in:
parent
6c1fd987f6
commit
ae863c62e3
|
@ -11746,8 +11746,8 @@ tcp-request content <action> [{if | unless} <condition>]
|
|||
A request's contents can be analyzed at an early stage of request processing
|
||||
called "TCP content inspection". During this stage, ACL-based rules are
|
||||
evaluated every time the request contents are updated, until either an
|
||||
"accept" or a "reject" rule matches, or the TCP request inspection delay
|
||||
expires with no matching rule.
|
||||
"accept", a "reject" or a "switch-mode" rule matches, or the TCP request
|
||||
inspection delay expires with no matching rule.
|
||||
|
||||
The first difference between these rules and "tcp-request connection" rules
|
||||
is that "tcp-request content" rules can make use of contents to take a
|
||||
|
@ -11780,6 +11780,7 @@ tcp-request content <action> [{if | unless} <condition>]
|
|||
- set-dst <expr>
|
||||
- set-dst-port <expr>
|
||||
- set-var(<var-name>) <expr>
|
||||
- switch-mode http [ proto <name> ]
|
||||
- unset-var(<var-name>)
|
||||
- silent-drop
|
||||
- send-spoe-group <engine-name> <group-name>
|
||||
|
@ -11849,6 +11850,17 @@ tcp-request content <action> [{if | unless} <condition>]
|
|||
<expr> Is a standard HAProxy expression formed by a sample-fetch
|
||||
followed by some converters.
|
||||
|
||||
The "switch-mode" is used to perform a conntection upgrade. Only HTTP
|
||||
upgrades are supported for now. The protocol may optionally be
|
||||
specified. This action is only available for a proxy with the frontend
|
||||
capability. The connection upgrade is immediately performed, following
|
||||
"tcp-request content" rules are not evaluated. This upgrade method should be
|
||||
preferred to the implicit one consisting to rely on the backend mode. When
|
||||
used, it is possible to set HTTP directives in a frontend without any
|
||||
warning. These directives will be conditionnaly evaluated if the HTTP upgrade
|
||||
is performed. However, an HTTP backend must still be selected. It remains
|
||||
unsupported to route an HTTP connection (upgraded or not) to a TCP server.
|
||||
|
||||
The "unset-var" is used to unset a variable. See above for details about
|
||||
<var-name>.
|
||||
|
||||
|
@ -11897,12 +11909,21 @@ tcp-request content <action> [{if | unless} <condition>]
|
|||
|
||||
Example:
|
||||
# Accept HTTP requests containing a Host header saying "example.com"
|
||||
# and reject everything else.
|
||||
# and reject everything else. (Only works for HTTP/1 connections)
|
||||
acl is_host_com hdr(Host) -i example.com
|
||||
tcp-request inspect-delay 30s
|
||||
tcp-request content accept if is_host_com
|
||||
tcp-request content reject
|
||||
|
||||
# Accept HTTP requests containing a Host header saying "example.com"
|
||||
# and reject everything else. (works for HTTP/1 and HTTP/2 connections)
|
||||
acl is_host_com hdr(Host) -i example.com
|
||||
tcp-request inspect-delay 5s
|
||||
tcp-request switch-mode http if HTTP
|
||||
tcp-request reject # non-HTTP traffic is implicit here
|
||||
...
|
||||
http-request reject unless is_host_com
|
||||
|
||||
Example:
|
||||
# reject SMTP connection if client speaks first
|
||||
tcp-request inspect-delay 30s
|
||||
|
|
|
@ -52,7 +52,6 @@
|
|||
#define SF_FORCE_PRST 0x00000010 /* force persistence here, even if server is down */
|
||||
#define SF_MONITOR 0x00000020 /* this stream comes from a monitoring system */
|
||||
#define SF_CURR_SESS 0x00000040 /* a connection is currently being counted on the server */
|
||||
/* unused: 0x00000080 */
|
||||
#define SF_REDISP 0x00000100 /* set if this stream was redispatched from one server to another */
|
||||
#define SF_IGNORE 0x00000200 /* The stream lead to a mux upgrade, and should be ignored */
|
||||
#define SF_REDIRECTABLE 0x00000400 /* set if this stream is redirectable (GET or HEAD) */
|
||||
|
|
|
@ -60,7 +60,7 @@ extern struct data_cb sess_conn_cb;
|
|||
struct stream *stream_new(struct session *sess, enum obj_type *origin, struct buffer *input);
|
||||
int stream_create_from_cs(struct conn_stream *cs, struct buffer *input);
|
||||
int stream_upgrade_from_cs(struct conn_stream *cs, struct buffer *input);
|
||||
int stream_set_http_mode(struct stream *s);
|
||||
int stream_set_http_mode(struct stream *s, const struct mux_proto_list *mux_proto);
|
||||
|
||||
/* kill a stream and set the termination flags to <why> (one of SF_ERR_*) */
|
||||
void stream_shutdown(struct stream *stream, int why);
|
||||
|
|
|
@ -2160,7 +2160,7 @@ int stream_set_backend(struct stream *s, struct proxy *be)
|
|||
if (!IS_HTX_STRM(s) && be->mode == PR_MODE_HTTP) {
|
||||
/* If we chain a TCP frontend to an HTX backend, we must upgrade
|
||||
* the client mux */
|
||||
if (!stream_set_http_mode(s))
|
||||
if (!stream_set_http_mode(s, NULL))
|
||||
return 0;
|
||||
}
|
||||
else if (IS_HTX_STRM(s) && be->mode != PR_MODE_HTTP) {
|
||||
|
|
113
src/stream.c
113
src/stream.c
|
@ -1481,9 +1481,9 @@ static int process_store_rules(struct stream *s, struct channel *rep, int an_bit
|
|||
/* Set the stream to HTTP mode, if necessary. The minimal request HTTP analysers
|
||||
* are set and the client mux is upgraded. It returns 1 if the stream processing
|
||||
* may continue or 0 if it should be stopped. It happens on error or if the
|
||||
* upgrade required a new stream.
|
||||
* upgrade required a new stream. The mux protocol may be specified.
|
||||
*/
|
||||
int stream_set_http_mode(struct stream *s)
|
||||
int stream_set_http_mode(struct stream *s, const struct mux_proto_list *mux_proto)
|
||||
{
|
||||
struct connection *conn;
|
||||
struct conn_stream *cs;
|
||||
|
@ -1508,9 +1508,12 @@ int stream_set_http_mode(struct stream *s)
|
|||
if (s->si[0].wait_event.events)
|
||||
conn->mux->unsubscribe(cs, s->si[0].wait_event.events,
|
||||
&s->si[0].wait_event);
|
||||
|
||||
if (conn->mux->flags & MX_FL_NO_UPG)
|
||||
return 0;
|
||||
if (conn_upgrade_mux_fe(conn, cs, &s->req.buf, ist(""), PROTO_MODE_HTTP) == -1)
|
||||
if (conn_upgrade_mux_fe(conn, cs, &s->req.buf,
|
||||
(mux_proto ? mux_proto->token : ist("")),
|
||||
PROTO_MODE_HTTP) == -1)
|
||||
return 0;
|
||||
|
||||
s->req.flags &= ~(CF_READ_PARTIAL|CF_AUTO_CONNECT);
|
||||
|
@ -2843,6 +2846,109 @@ struct ist stream_generate_unique_id(struct stream *strm, struct list *format)
|
|||
/************************************************************************/
|
||||
/* All supported ACL keywords must be declared here. */
|
||||
/************************************************************************/
|
||||
static enum act_return tcp_action_switch_stream_mode(struct act_rule *rule, struct proxy *px,
|
||||
struct session *sess, struct stream *s, int flags)
|
||||
{
|
||||
enum pr_mode mode = (uintptr_t)rule->arg.act.p[0];
|
||||
const struct mux_proto_list *mux_proto = rule->arg.act.p[1];
|
||||
|
||||
if (!IS_HTX_STRM(s) && mode == PR_MODE_HTTP) {
|
||||
if (!stream_set_http_mode(s, mux_proto)) {
|
||||
channel_abort(&s->req);
|
||||
channel_abort(&s->res);
|
||||
return ACT_RET_ABRT;
|
||||
}
|
||||
}
|
||||
return ACT_RET_STOP;
|
||||
}
|
||||
|
||||
|
||||
static int check_tcp_switch_stream_mode(struct act_rule *rule, struct proxy *px, char **err)
|
||||
{
|
||||
const struct mux_proto_list *mux_ent;
|
||||
const struct mux_proto_list *mux_proto = rule->arg.act.p[1];
|
||||
enum pr_mode pr_mode = (uintptr_t)rule->arg.act.p[0];
|
||||
enum proto_proxy_mode mode = (1 << (pr_mode == PR_MODE_HTTP));
|
||||
|
||||
if (mux_proto) {
|
||||
mux_ent = conn_get_best_mux_entry(mux_proto->token, PROTO_SIDE_FE, mode);
|
||||
if (!mux_ent || !isteq(mux_ent->token, mux_proto->token)) {
|
||||
memprintf(err, "MUX protocol '%.*s' is not compatible with the selected mode",
|
||||
(int)mux_proto->token.len, mux_proto->token.ptr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
mux_ent = conn_get_best_mux_entry(IST_NULL, PROTO_SIDE_FE, mode);
|
||||
if (!mux_ent) {
|
||||
memprintf(err, "Unable to find compatible MUX protocol with the selected mode");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the mux */
|
||||
rule->arg.act.p[1] = (void *)mux_ent;
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
static enum act_parse_ret stream_parse_switch_mode(const char **args, int *cur_arg,
|
||||
struct proxy *px, struct act_rule *rule,
|
||||
char **err)
|
||||
{
|
||||
const struct mux_proto_list *mux_proto = NULL;
|
||||
struct ist proto;
|
||||
enum pr_mode mode;
|
||||
|
||||
/* must have at least the mode */
|
||||
if (*(args[*cur_arg]) == 0) {
|
||||
memprintf(err, "'%s %s' expects a mode as argument.", args[0], args[*cur_arg-1]);
|
||||
return ACT_RET_PRS_ERR;
|
||||
}
|
||||
|
||||
if (!(px->cap & PR_CAP_FE)) {
|
||||
memprintf(err, "'%s %s' not allowed because %s '%s' has no frontend capability",
|
||||
args[0], args[*cur_arg-1], proxy_type_str(px), px->id);
|
||||
return ACT_RET_PRS_ERR;
|
||||
}
|
||||
/* Check if the mode. For now "tcp" is disabled because downgrade is not
|
||||
* supported and PT is the only TCP mux.
|
||||
*/
|
||||
if (strcmp(args[*cur_arg], "http") == 0)
|
||||
mode = PR_MODE_HTTP;
|
||||
else {
|
||||
memprintf(err, "'%s %s' expects a valid mode (got '%s').", args[0], args[*cur_arg-1], args[*cur_arg]);
|
||||
return ACT_RET_PRS_ERR;
|
||||
}
|
||||
|
||||
/* check the proto, if specified */
|
||||
if (*(args[*cur_arg+1]) && strcmp(args[*cur_arg+1], "proto") == 0) {
|
||||
if (*(args[*cur_arg+2]) == 0) {
|
||||
memprintf(err, "'%s %s': '%s' expects a protocol as argument.",
|
||||
args[0], args[*cur_arg-1], args[*cur_arg+1]);
|
||||
return ACT_RET_PRS_ERR;
|
||||
}
|
||||
|
||||
proto = ist2(args[*cur_arg+2], strlen(args[*cur_arg+2]));
|
||||
mux_proto = get_mux_proto(proto);
|
||||
if (!mux_proto) {
|
||||
memprintf(err, "'%s %s': '%s' expects a valid MUX protocol, if specified (got '%s')",
|
||||
args[0], args[*cur_arg-1], args[*cur_arg+1], args[*cur_arg+2]);
|
||||
return ACT_RET_PRS_ERR;
|
||||
}
|
||||
*cur_arg += 2;
|
||||
}
|
||||
|
||||
(*cur_arg)++;
|
||||
|
||||
/* Register processing function. */
|
||||
rule->action_ptr = tcp_action_switch_stream_mode;
|
||||
rule->check_ptr = check_tcp_switch_stream_mode;
|
||||
rule->action = ACT_CUSTOM;
|
||||
rule->arg.act.p[0] = (void *)(uintptr_t)mode;
|
||||
rule->arg.act.p[1] = (void *)mux_proto;
|
||||
return ACT_RET_PRS_OK;
|
||||
}
|
||||
|
||||
/* 0=OK, <0=Alert, >0=Warning */
|
||||
static enum act_parse_ret stream_parse_use_service(const char **args, int *cur_arg,
|
||||
|
@ -3593,6 +3699,7 @@ INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
|
|||
|
||||
/* main configuration keyword registration. */
|
||||
static struct action_kw_list stream_tcp_keywords = { ILH, {
|
||||
{ "switch-mode", stream_parse_switch_mode },
|
||||
{ "use-service", stream_parse_use_service },
|
||||
{ /* END */ }
|
||||
}};
|
||||
|
|
Loading…
Reference in New Issue