MEDIUM: tcp: add registration and processing of TCP L5 rules

This commit introduces "tcp-request session" rules. These are very
much like "tcp-request connection" rules except that they're processed
after the handshake, so it is possible to consider SSL information and
addresses rewritten by the proxy protocol header in actions. This is
particularly useful to track proxied sources as this was not possible
before, given that tcp-request content rules are processed after each
HTTP request. Similarly it is possible to assign the proxied source
address or the client's cert to a variable.
This commit is contained in:
Willy Tarreau 2016-10-21 16:37:51 +02:00
parent 27df66e8d9
commit 620408f406
11 changed files with 253 additions and 4 deletions

View File

@ -75,6 +75,7 @@ int cfg_register_section(char *section_name,
int (*section_parser)(const char *, int, char **, int));
void cfg_unregister_sections(void);
int warnif_misplaced_tcp_conn(struct proxy *proxy, const char *file, int line, const char *arg);
int warnif_misplaced_tcp_sess(struct proxy *proxy, const char *file, int line, const char *arg);
int warnif_misplaced_tcp_cont(struct proxy *proxy, const char *file, int line, const char *arg);
/*

View File

@ -39,9 +39,11 @@ int tcp_drain(int fd);
int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit);
int tcp_inspect_response(struct stream *s, struct channel *rep, int an_bit);
int tcp_exec_l4_rules(struct session *sess);
int tcp_exec_l5_rules(struct session *sess);
/* TCP keywords. */
void tcp_req_conn_keywords_register(struct action_kw_list *kw_list);
void tcp_req_sess_keywords_register(struct action_kw_list *kw_list);
void tcp_req_cont_keywords_register(struct action_kw_list *kw_list);
void tcp_res_cont_keywords_register(struct action_kw_list *kw_list);

View File

@ -29,6 +29,7 @@
enum act_from {
ACT_F_TCP_REQ_CON, /* tcp-request connection */
ACT_F_TCP_REQ_SES, /* tcp-request session */
ACT_F_TCP_REQ_CNT, /* tcp-request content */
ACT_F_TCP_RES_CNT, /* tcp-response content */
ACT_F_HTTP_REQ, /* http-request */

View File

@ -86,6 +86,7 @@ enum li_state {
#define LI_O_NOQUICKACK 0x0004 /* disable quick ack of immediate data (linux) */
#define LI_O_DEF_ACCEPT 0x0008 /* wait up to 1 second for data before accepting */
#define LI_O_TCP_L4_RULES 0x0010 /* run TCP L4 rules checks on the incoming connection */
#define LI_O_TCP_L5_RULES 0x0020 /* run TCP L5 rules checks on the incoming session */
#define LI_O_CHK_MONNET 0x0040 /* check the source against a monitor-net rule */
#define LI_O_ACC_PROXY 0x0080 /* find the proxied address in the first request line */
#define LI_O_UNLIMITED 0x0100 /* listener not subject to global limits (peers & stats socket) */

View File

@ -269,6 +269,7 @@ struct proxy {
unsigned int inspect_delay; /* inspection delay */
struct list inspect_rules; /* inspection rules */
struct list l4_rules; /* layer4 rules */
struct list l5_rules; /* layer5 rules */
} tcp_req;
struct { /* TCP request processing */
unsigned int inspect_delay; /* inspection delay */

View File

@ -358,6 +358,19 @@ int alertif_too_many_args(int maxarg, const char *file, int linenum, char **args
return alertif_too_many_args_idx(maxarg, 0, file, linenum, args, err_code);
}
/* Report a warning if a rule is placed after a 'tcp-request session' rule.
* Return 1 if the warning has been emitted, otherwise 0.
*/
int warnif_rule_after_tcp_sess(struct proxy *proxy, const char *file, int line, const char *arg)
{
if (!LIST_ISEMPTY(&proxy->tcp_req.l5_rules)) {
Warning("parsing [%s:%d] : a '%s' rule placed after a 'tcp-request session' rule will still be processed before.\n",
file, line, arg);
return 1;
}
return 0;
}
/* Report a warning if a rule is placed after a 'tcp-request content' rule.
* Return 1 if the warning has been emitted, otherwise 0.
*/
@ -464,6 +477,19 @@ int warnif_rule_after_use_server(struct proxy *proxy, const char *file, int line
/* report a warning if a "tcp request connection" rule is dangerously placed */
int warnif_misplaced_tcp_conn(struct proxy *proxy, const char *file, int line, const char *arg)
{
return warnif_rule_after_tcp_sess(proxy, file, line, arg) ||
warnif_rule_after_tcp_cont(proxy, file, line, arg) ||
warnif_rule_after_block(proxy, file, line, arg) ||
warnif_rule_after_http_req(proxy, file, line, arg) ||
warnif_rule_after_reqxxx(proxy, file, line, arg) ||
warnif_rule_after_reqadd(proxy, file, line, arg) ||
warnif_rule_after_redirect(proxy, file, line, arg) ||
warnif_rule_after_use_backend(proxy, file, line, arg) ||
warnif_rule_after_use_server(proxy, file, line, arg);
}
int warnif_misplaced_tcp_sess(struct proxy *proxy, const char *file, int line, const char *arg)
{
return warnif_rule_after_tcp_cont(proxy, file, line, arg) ||
warnif_rule_after_block(proxy, file, line, arg) ||
@ -7810,6 +7836,45 @@ int check_config_validity()
}
}
/* find the target table for 'tcp-request' layer 5 rules */
list_for_each_entry(trule, &curproxy->tcp_req.l5_rules, list) {
struct proxy *target;
if (trule->action < ACT_ACTION_TRK_SC0 || trule->action > ACT_ACTION_TRK_SCMAX)
continue;
if (trule->arg.trk_ctr.table.n)
target = proxy_tbl_by_name(trule->arg.trk_ctr.table.n);
else
target = curproxy;
if (!target) {
Alert("Proxy '%s': unable to find table '%s' referenced by track-sc%d.\n",
curproxy->id, trule->arg.trk_ctr.table.n,
tcp_trk_idx(trule->action));
cfgerr++;
}
else if (target->table.size == 0) {
Alert("Proxy '%s': table '%s' used but not configured.\n",
curproxy->id, trule->arg.trk_ctr.table.n ? trule->arg.trk_ctr.table.n : curproxy->id);
cfgerr++;
}
else if (!stktable_compatible_sample(trule->arg.trk_ctr.expr, target->table.type)) {
Alert("Proxy '%s': stick-table '%s' uses a type incompatible with the 'track-sc%d' rule.\n",
curproxy->id, trule->arg.trk_ctr.table.n ? trule->arg.trk_ctr.table.n : curproxy->id,
tcp_trk_idx(trule->action));
cfgerr++;
}
else {
free(trule->arg.trk_ctr.table.n);
trule->arg.trk_ctr.table.t = &target->table;
/* Note: if we decide to enhance the track-sc syntax, we may be able
* to pass a list of counters to track and allocate them right here using
* stktable_alloc_data_type().
*/
}
}
/* find the target table for 'tcp-request' layer 6 rules */
list_for_each_entry(trule, &curproxy->tcp_req.inspect_rules, list) {
struct proxy *target;
@ -8830,6 +8895,9 @@ out_uri_auth_compat:
if (!LIST_ISEMPTY(&curproxy->tcp_req.l4_rules))
listener->options |= LI_O_TCP_L4_RULES;
if (!LIST_ISEMPTY(&curproxy->tcp_req.l5_rules))
listener->options |= LI_O_TCP_L5_RULES;
if (curproxy->mon_mask.s_addr)
listener->options |= LI_O_CHK_MONNET;

View File

@ -70,6 +70,7 @@ static int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen
/* List head of all known action keywords for "tcp-request connection" */
struct list tcp_req_conn_keywords = LIST_HEAD_INIT(tcp_req_conn_keywords);
struct list tcp_req_sess_keywords = LIST_HEAD_INIT(tcp_req_sess_keywords);
struct list tcp_req_cont_keywords = LIST_HEAD_INIT(tcp_req_cont_keywords);
struct list tcp_res_cont_keywords = LIST_HEAD_INIT(tcp_res_cont_keywords);
@ -127,6 +128,11 @@ void tcp_req_conn_keywords_register(struct action_kw_list *kw_list)
LIST_ADDQ(&tcp_req_conn_keywords, &kw_list->list);
}
void tcp_req_sess_keywords_register(struct action_kw_list *kw_list)
{
LIST_ADDQ(&tcp_req_sess_keywords, &kw_list->list);
}
void tcp_req_cont_keywords_register(struct action_kw_list *kw_list)
{
LIST_ADDQ(&tcp_req_cont_keywords, &kw_list->list);
@ -138,13 +144,18 @@ void tcp_res_cont_keywords_register(struct action_kw_list *kw_list)
}
/*
* Return the struct http_req_action_kw associated to a keyword.
* Return the struct tcp_req_action_kw associated to a keyword.
*/
static struct action_kw *tcp_req_conn_action(const char *kw)
{
return action_lookup(&tcp_req_conn_keywords, kw);
}
static struct action_kw *tcp_req_sess_action(const char *kw)
{
return action_lookup(&tcp_req_sess_keywords, kw);
}
static struct action_kw *tcp_req_cont_action(const char *kw)
{
return action_lookup(&tcp_req_cont_keywords, kw);
@ -1437,6 +1448,85 @@ int tcp_exec_l4_rules(struct session *sess)
return result;
}
/* This function performs the TCP layer5 analysis on the current request. It
* returns 0 if a reject rule matches, otherwise 1 if either an accept rule
* matches or if no more rule matches. It can only use rules which don't need
* any data. This only works on session-based client-facing stream interfaces.
* An example of valid use case is to track a stick-counter on the source
* address extracted from the proxy protocol.
*/
int tcp_exec_l5_rules(struct session *sess)
{
struct act_rule *rule;
struct stksess *ts;
struct stktable *t = NULL;
int result = 1;
enum acl_test_res ret;
list_for_each_entry(rule, &sess->fe->tcp_req.l5_rules, list) {
ret = ACL_TEST_PASS;
if (rule->cond) {
ret = acl_exec_cond(rule->cond, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL);
ret = acl_pass(ret);
if (rule->cond->pol == ACL_COND_UNLESS)
ret = !ret;
}
if (ret) {
/* we have a matching rule. */
if (rule->action == ACT_ACTION_ALLOW) {
break;
}
else if (rule->action == ACT_ACTION_DENY) {
sess->fe->fe_counters.denied_sess++;
if (sess->listener->counters)
sess->listener->counters->denied_sess++;
result = 0;
break;
}
else if (rule->action >= ACT_ACTION_TRK_SC0 && rule->action <= ACT_ACTION_TRK_SCMAX) {
/* Note: only the first valid tracking parameter of each
* applies.
*/
struct stktable_key *key;
if (stkctr_entry(&sess->stkctr[tcp_trk_idx(rule->action)]))
continue;
t = rule->arg.trk_ctr.table.t;
key = stktable_fetch_key(t, sess->fe, sess, NULL, SMP_OPT_DIR_REQ|SMP_OPT_FINAL, rule->arg.trk_ctr.expr, NULL);
if (key && (ts = stktable_get_entry(t, key)))
stream_track_stkctr(&sess->stkctr[tcp_trk_idx(rule->action)], t, ts);
}
else {
/* Custom keywords. */
if (!rule->action_ptr)
break;
switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_FLAG_FINAL | ACT_FLAG_FIRST)) {
case ACT_RET_YIELD:
/* yield is not allowed at this point. If this return code is
* used it is a bug, so I prefer to abort the process.
*/
send_log(sess->fe, LOG_WARNING,
"Internal error: yield not allowed with tcp-request session actions.");
case ACT_RET_STOP:
break;
case ACT_RET_CONT:
continue;
case ACT_RET_ERR:
result = 0;
break;
}
break; /* ACT_RET_STOP */
}
}
}
return result;
}
/*
* 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
@ -1885,6 +1975,11 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type,
kw = tcp_req_conn_action(args[arg]);
rule->kw = kw;
rule->from = ACT_F_TCP_REQ_CON;
} else if (where & SMP_VAL_FE_SES_ACC) {
/* L5 */
kw = tcp_req_sess_action(args[arg]);
rule->kw = kw;
rule->from = ACT_F_TCP_REQ_SES;
} else {
/* L6 */
kw = tcp_req_cont_action(args[arg]);
@ -1898,6 +1993,8 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type,
} else {
if (where & SMP_VAL_FE_CON_ACC)
action_build_list(&tcp_req_conn_keywords, &trash);
else if (where & SMP_VAL_FE_SES_ACC)
action_build_list(&tcp_req_sess_keywords, &trash);
else
action_build_list(&tcp_req_cont_keywords, &trash);
memprintf(err,
@ -2174,6 +2271,50 @@ static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
warnif_misplaced_tcp_conn(curpx, file, line, args[0]);
LIST_ADDQ(&curpx->tcp_req.l4_rules, &rule->list);
}
else if (strcmp(args[1], "session") == 0) {
arg++;
if (!(curpx->cap & PR_CAP_FE)) {
memprintf(err, "%s %s is not allowed because %s %s is not a frontend",
args[0], args[1], proxy_type_str(curpx), curpx->id);
goto error;
}
where |= SMP_VAL_FE_SES_ACC;
if (tcp_parse_request_rule(args, arg, section_type, curpx, defpx, rule, err, where, file, line) < 0)
goto error;
acl = rule->cond ? acl_cond_conflicts(rule->cond, where) : NULL;
if (acl) {
if (acl->name && *acl->name)
memprintf(err,
"acl '%s' will never match in '%s %s' because it only involves keywords that are incompatible with '%s'",
acl->name, args[0], args[1], sample_ckp_names(where));
else
memprintf(err,
"anonymous acl will never match in '%s %s' because it uses keyword '%s' which is incompatible with '%s'",
args[0], args[1],
LIST_ELEM(acl->expr.n, struct acl_expr *, list)->kw,
sample_ckp_names(where));
warn++;
}
else if (rule->cond && acl_cond_kw_conflicts(rule->cond, where, &acl, &kw)) {
if (acl->name && *acl->name)
memprintf(err,
"acl '%s' involves keyword '%s' which is incompatible with '%s'",
acl->name, kw, sample_ckp_names(where));
else
memprintf(err,
"anonymous acl involves keyword '%s' which is incompatible with '%s'",
kw, sample_ckp_names(where));
warn++;
}
/* the following function directly emits the warning */
warnif_misplaced_tcp_sess(curpx, file, line, args[0]);
LIST_ADDQ(&curpx->tcp_req.l5_rules, &rule->list);
}
else {
if (curpx == defpx)
memprintf(err,
@ -2844,6 +2985,15 @@ static struct action_kw_list tcp_req_conn_actions = {ILH, {
{ /* END */ }
}};
static struct action_kw_list tcp_req_sess_actions = {ILH, {
{ "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 },
{ /* END */ }
}};
static struct action_kw_list tcp_req_cont_actions = {ILH, {
{ "silent-drop", tcp_parse_silent_drop },
{ /* END */ }
@ -2880,6 +3030,7 @@ static void __tcp_protocol_init(void)
bind_register_keywords(&bind_kws);
srv_register_keywords(&srv_kws);
tcp_req_conn_keywords_register(&tcp_req_conn_actions);
tcp_req_sess_keywords_register(&tcp_req_sess_actions);
tcp_req_cont_keywords_register(&tcp_req_cont_actions);
tcp_res_cont_keywords_register(&tcp_res_cont_actions);
http_req_keywords_register(&http_req_actions);

View File

@ -737,6 +737,7 @@ void init_new_proxy(struct proxy *p)
LIST_INIT(&p->tcp_req.inspect_rules);
LIST_INIT(&p->tcp_rep.inspect_rules);
LIST_INIT(&p->tcp_req.l4_rules);
LIST_INIT(&p->tcp_req.l5_rules);
LIST_INIT(&p->req_add);
LIST_INIT(&p->rsp_add);
LIST_INIT(&p->listener_queue);

View File

@ -267,6 +267,10 @@ int session_accept_fd(struct listener *l, int cfd, struct sockaddr_storage *addr
if (sess->fe->to_log & LW_XPRT)
cli_conn->flags |= CO_FL_XPRT_TRACKED;
/* we may have some tcp-request-session rules */
if ((l->options & LI_O_TCP_L5_RULES) && !tcp_exec_l5_rules(sess))
goto out_free_sess;
session_count_new(sess);
strm = stream_new(sess, t, &cli_conn->obj_type);
if (!strm)
@ -435,6 +439,10 @@ static int conn_complete_session(struct connection *conn)
if (sess->fe->to_log & LW_XPRT)
conn->flags |= CO_FL_XPRT_TRACKED;
/* we may have some tcp-request-session rules */
if ((sess->listener->options & LI_O_TCP_L5_RULES) && !tcp_exec_l5_rules(sess))
goto fail;
session_count_new(sess);
task->process = sess->listener->handler;
strm = stream_new(sess, task, &conn->obj_type);

View File

@ -1519,6 +1519,12 @@ static struct action_kw_list tcp_conn_kws = { { }, {
{ /* END */ }
}};
static struct action_kw_list tcp_sess_kws = { { }, {
{ "sc-inc-gpc0", parse_inc_gpc0, 1 },
{ "sc-set-gpt0", parse_set_gpt0, 1 },
{ /* END */ }
}};
static struct action_kw_list tcp_req_kws = { { }, {
{ "sc-inc-gpc0", parse_inc_gpc0, 1 },
{ "sc-set-gpt0", parse_set_gpt0, 1 },
@ -1572,6 +1578,7 @@ static void __stick_table_init(void)
{
/* register som action keywords. */
tcp_req_conn_keywords_register(&tcp_conn_kws);
tcp_req_sess_keywords_register(&tcp_sess_kws);
tcp_req_cont_keywords_register(&tcp_req_kws);
tcp_res_cont_keywords_register(&tcp_res_kws);
http_req_keywords_register(&http_req_kws);

View File

@ -359,7 +359,7 @@ static int sample_store(struct vars *vars, const char *name, struct sample *smp)
return 1;
}
/* Returns 0 if fails, else returns 1. */
/* Returns 0 if fails, else returns 1. Note that stream may be null for SCOPE_SESS. */
static inline int sample_store_stream(const char *name, enum vars_scope scope, struct sample *smp)
{
struct vars *vars;
@ -504,6 +504,7 @@ static enum act_return action_store(struct act_rule *rule, struct proxy *px,
int dir;
switch (rule->from) {
case ACT_F_TCP_REQ_SES: dir = SMP_OPT_DIR_REQ; break;
case ACT_F_TCP_REQ_CNT: dir = SMP_OPT_DIR_REQ; break;
case ACT_F_TCP_RES_CNT: dir = SMP_OPT_DIR_RES; break;
case ACT_F_HTTP_REQ: dir = SMP_OPT_DIR_REQ; break;
@ -587,6 +588,7 @@ static enum act_parse_ret parse_store(const char **args, int *arg, struct proxy
return ACT_RET_PRS_ERR;
switch (rule->from) {
case ACT_F_TCP_REQ_SES: flags = SMP_VAL_FE_SES_ACC; break;
case ACT_F_TCP_REQ_CNT: flags = SMP_VAL_FE_REQ_CNT; break;
case ACT_F_TCP_RES_CNT: flags = SMP_VAL_BE_RES_CNT; break;
case ACT_F_HTTP_REQ: flags = SMP_VAL_FE_HRQ_HDR; break;
@ -663,7 +665,12 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ /* END */ },
}};
static struct action_kw_list tcp_req_kws = { { }, {
static struct action_kw_list tcp_req_sess_kws = { { }, {
{ "set-var", parse_store, 1 },
{ /* END */ }
}};
static struct action_kw_list tcp_req_cont_kws = { { }, {
{ "set-var", parse_store, 1 },
{ /* END */ }
}};
@ -698,7 +705,8 @@ static void __http_protocol_init(void)
sample_register_fetches(&sample_fetch_keywords);
sample_register_convs(&sample_conv_kws);
tcp_req_cont_keywords_register(&tcp_req_kws);
tcp_req_sess_keywords_register(&tcp_req_sess_kws);
tcp_req_cont_keywords_register(&tcp_req_cont_kws);
tcp_res_cont_keywords_register(&tcp_res_kws);
http_req_keywords_register(&http_req_kws);
http_res_keywords_register(&http_res_kws);