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);
/*
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);
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 */
#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) */
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 */
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.
*/
/* 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) ||
}
}
+ /* 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;
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;
/* 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);
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);
}
/*
- * 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);
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
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]);
} 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,
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,
{ /* 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 */ }
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);
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);
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)
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);
{ /* 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 },
{
/* 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);
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;
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;
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;
{ /* 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 */ }
}};
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);