diff --git a/include/common/cfgparse.h b/include/common/cfgparse.h index d78532771..6dc6ad586 100644 --- a/include/common/cfgparse.h +++ b/include/common/cfgparse.h @@ -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); /* diff --git a/include/proto/proto_tcp.h b/include/proto/proto_tcp.h index 05b6e0268..f5b9e5536 100644 --- a/include/proto/proto_tcp.h +++ b/include/proto/proto_tcp.h @@ -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); diff --git a/include/types/action.h b/include/types/action.h index fce6bc87b..5a70db06c 100644 --- a/include/types/action.h +++ b/include/types/action.h @@ -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 */ diff --git a/include/types/listener.h b/include/types/listener.h index 8cfe40b8e..1f14cc0bd 100644 --- a/include/types/listener.h +++ b/include/types/listener.h @@ -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) */ diff --git a/include/types/proxy.h b/include/types/proxy.h index e23b4e4bf..2f4f9b9cb 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -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 */ diff --git a/src/cfgparse.c b/src/cfgparse.c index 17f9d19a4..cc2f507e7 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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; diff --git a/src/proto_tcp.c b/src/proto_tcp.c index 0223b8e3d..8b3d546fd 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -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); diff --git a/src/proxy.c b/src/proxy.c index b90773fa4..1955d823d 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -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); diff --git a/src/session.c b/src/session.c index d160a0574..cdf57e388 100644 --- a/src/session.c +++ b/src/session.c @@ -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); diff --git a/src/stick_table.c b/src/stick_table.c index b269bc1ea..7a2fcc21a 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -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); diff --git a/src/vars.c b/src/vars.c index a3dd85ce4..6f9b16aa9 100644 --- a/src/vars.c +++ b/src/vars.c @@ -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);