haproxy/src/tcp_rules.c
Christopher Faulet 4507351a2f BUG/MINOR: build: Fix compilation with debug mode enabled
It remained some fragments of the old buffers API in debug messages, here and
there.

This was caused by the recent buffer API changes, no backport is needed.
2018-07-20 10:45:20 +02:00

1207 lines
35 KiB
C

/*
* "tcp" rules processing
*
* Copyright 2000-2016 Willy Tarreau <w@1wt.eu>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <common/cfgparse.h>
#include <common/compat.h>
#include <common/config.h>
#include <common/debug.h>
#include <common/mini-clist.h>
#include <common/standard.h>
#include <common/ticks.h>
#include <common/time.h>
#include <types/arg.h>
#include <types/capture.h>
#include <types/connection.h>
#include <types/global.h>
#include <proto/acl.h>
#include <proto/action.h>
#include <proto/channel.h>
#include <proto/connection.h>
#include <proto/log.h>
#include <proto/proxy.h>
#include <proto/sample.h>
#include <proto/stick_table.h>
#include <proto/stream.h>
#include <proto/stream_interface.h>
#include <proto/tcp_rules.h>
/* 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);
/*
* Register keywords.
*/
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);
}
void tcp_res_cont_keywords_register(struct action_kw_list *kw_list)
{
LIST_ADDQ(&tcp_res_cont_keywords, &kw_list->list);
}
/*
* 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);
}
static struct action_kw *tcp_res_cont_action(const char *kw)
{
return action_lookup(&tcp_res_cont_keywords, kw);
}
/* This function performs the TCP request analysis on the current request. It
* returns 1 if the processing can continue on next analysers, or zero if it
* needs more data, encounters an error, or wants to immediately abort the
* request. It relies on buffers flags, and updates s->req->analysers. The
* function may be called for frontend rules and backend rules. It only relies
* on the backend pointer so this works for both cases.
*/
int tcp_inspect_request(struct stream *s, struct channel *req, int an_bit)
{
struct session *sess = s->sess;
struct act_rule *rule;
struct stksess *ts;
struct stktable *t;
int partial;
int act_flags = 0;
DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n",
now_ms, __FUNCTION__,
s,
req,
req->rex, req->wex,
req->flags,
ci_data(req),
req->analysers);
/* We don't know whether we have enough data, so must proceed
* this way :
* - iterate through all rules in their declaration order
* - if one rule returns MISS, it means the inspect delay is
* not over yet, then return immediately, otherwise consider
* it as a non-match.
* - if one rule returns OK, then return OK
* - if one rule returns KO, then return KO
*/
if ((req->flags & CF_SHUTR) || channel_full(req, global.tune.maxrewrite) ||
!s->be->tcp_req.inspect_delay || tick_is_expired(req->analyse_exp, now_ms))
partial = SMP_OPT_FINAL;
else
partial = 0;
/* If "the current_rule_list" match the executed rule list, we are in
* resume condition. If a resume is needed it is always in the action
* and never in the ACL or converters. In this case, we initialise the
* current rule, and go to the action execution point.
*/
if (s->current_rule) {
rule = s->current_rule;
s->current_rule = NULL;
if (s->current_rule_list == &s->be->tcp_req.inspect_rules)
goto resume_execution;
}
s->current_rule_list = &s->be->tcp_req.inspect_rules;
list_for_each_entry(rule, &s->be->tcp_req.inspect_rules, list) {
enum acl_test_res ret = ACL_TEST_PASS;
if (rule->cond) {
ret = acl_exec_cond(rule->cond, s->be, sess, s, SMP_OPT_DIR_REQ | partial);
if (ret == ACL_TEST_MISS)
goto missing_data;
ret = acl_pass(ret);
if (rule->cond->pol == ACL_COND_UNLESS)
ret = !ret;
}
if (ret) {
act_flags |= ACT_FLAG_FIRST;
resume_execution:
/* we have a matching rule. */
if (rule->action == ACT_ACTION_ALLOW) {
break;
}
else if (rule->action == ACT_ACTION_DENY) {
channel_abort(req);
channel_abort(&s->res);
req->analysers = 0;
HA_ATOMIC_ADD(&s->be->be_counters.denied_req, 1);
HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_req, 1);
if (sess->listener && sess->listener->counters)
HA_ATOMIC_ADD(&sess->listener->counters->denied_req, 1);
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
if (!(s->flags & SF_FINST_MASK))
s->flags |= SF_FINST_R;
return 0;
}
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;
struct sample smp;
if (stkctr_entry(&s->stkctr[trk_idx(rule->action)]))
continue;
t = rule->arg.trk_ctr.table.t;
key = stktable_fetch_key(t, s->be, sess, s, SMP_OPT_DIR_REQ | partial, rule->arg.trk_ctr.expr, &smp);
if ((smp.flags & SMP_F_MAY_CHANGE) && !(partial & SMP_OPT_FINAL))
goto missing_data; /* key might appear later */
if (key && (ts = stktable_get_entry(t, key))) {
stream_track_stkctr(&s->stkctr[trk_idx(rule->action)], t, ts);
stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_CONTENT);
if (sess->fe != s->be)
stkctr_set_flags(&s->stkctr[trk_idx(rule->action)], STKCTR_TRACK_BACKEND);
}
}
else if (rule->action == ACT_TCP_CAPTURE) {
struct sample *key;
struct cap_hdr *h = rule->arg.cap.hdr;
char **cap = s->req_cap;
int len;
key = sample_fetch_as_type(s->be, sess, s, SMP_OPT_DIR_REQ | partial, rule->arg.cap.expr, SMP_T_STR);
if (!key)
continue;
if (key->flags & SMP_F_MAY_CHANGE)
goto missing_data;
if (cap[h->index] == NULL)
cap[h->index] = pool_alloc(h->pool);
if (cap[h->index] == NULL) /* no more capture memory */
continue;
len = key->data.u.str.data;
if (len > h->len)
len = h->len;
memcpy(cap[h->index], key->data.u.str.area,
len);
cap[h->index][len] = 0;
}
else {
/* Custom keywords. */
if (!rule->action_ptr)
continue;
if (partial & SMP_OPT_FINAL)
act_flags |= ACT_FLAG_FINAL;
switch (rule->action_ptr(rule, s->be, s->sess, s, act_flags)) {
case ACT_RET_ERR:
case ACT_RET_CONT:
continue;
case ACT_RET_STOP:
break;
case ACT_RET_YIELD:
s->current_rule = rule;
goto missing_data;
}
break; /* ACT_RET_STOP */
}
}
}
/* if we get there, it means we have no rule which matches, or
* we have an explicit accept, so we apply the default accept.
*/
req->analysers &= ~an_bit;
req->analyse_exp = TICK_ETERNITY;
return 1;
missing_data:
channel_dont_connect(req);
/* just set the request timeout once at the beginning of the request */
if (!tick_isset(req->analyse_exp) && s->be->tcp_req.inspect_delay)
req->analyse_exp = tick_add(now_ms, s->be->tcp_req.inspect_delay);
return 0;
}
/* This function performs the TCP response analysis on the current response. It
* returns 1 if the processing can continue on next analysers, or zero if it
* needs more data, encounters an error, or wants to immediately abort the
* response. It relies on buffers flags, and updates s->rep->analysers. The
* function may be called for backend rules.
*/
int tcp_inspect_response(struct stream *s, struct channel *rep, int an_bit)
{
struct session *sess = s->sess;
struct act_rule *rule;
int partial;
int act_flags = 0;
DPRINTF(stderr,"[%u] %s: stream=%p b=%p, exp(r,w)=%u,%u bf=%08x bh=%lu analysers=%02x\n",
now_ms, __FUNCTION__,
s,
rep,
rep->rex, rep->wex,
rep->flags,
ci_data(rep),
rep->analysers);
/* We don't know whether we have enough data, so must proceed
* this way :
* - iterate through all rules in their declaration order
* - if one rule returns MISS, it means the inspect delay is
* not over yet, then return immediately, otherwise consider
* it as a non-match.
* - if one rule returns OK, then return OK
* - if one rule returns KO, then return KO
*/
if (rep->flags & CF_SHUTR || tick_is_expired(rep->analyse_exp, now_ms))
partial = SMP_OPT_FINAL;
else
partial = 0;
/* If "the current_rule_list" match the executed rule list, we are in
* resume condition. If a resume is needed it is always in the action
* and never in the ACL or converters. In this case, we initialise the
* current rule, and go to the action execution point.
*/
if (s->current_rule) {
rule = s->current_rule;
s->current_rule = NULL;
if (s->current_rule_list == &s->be->tcp_rep.inspect_rules)
goto resume_execution;
}
s->current_rule_list = &s->be->tcp_rep.inspect_rules;
list_for_each_entry(rule, &s->be->tcp_rep.inspect_rules, list) {
enum acl_test_res ret = ACL_TEST_PASS;
if (rule->cond) {
ret = acl_exec_cond(rule->cond, s->be, sess, s, SMP_OPT_DIR_RES | partial);
if (ret == ACL_TEST_MISS) {
/* just set the analyser timeout once at the beginning of the response */
if (!tick_isset(rep->analyse_exp) && s->be->tcp_rep.inspect_delay)
rep->analyse_exp = tick_add(now_ms, s->be->tcp_rep.inspect_delay);
return 0;
}
ret = acl_pass(ret);
if (rule->cond->pol == ACL_COND_UNLESS)
ret = !ret;
}
if (ret) {
act_flags |= ACT_FLAG_FIRST;
resume_execution:
/* we have a matching rule. */
if (rule->action == ACT_ACTION_ALLOW) {
break;
}
else if (rule->action == ACT_ACTION_DENY) {
channel_abort(rep);
channel_abort(&s->req);
rep->analysers = 0;
HA_ATOMIC_ADD(&s->be->be_counters.denied_resp, 1);
HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_resp, 1);
if (sess->listener && sess->listener->counters)
HA_ATOMIC_ADD(&sess->listener->counters->denied_resp, 1);
if (!(s->flags & SF_ERR_MASK))
s->flags |= SF_ERR_PRXCOND;
if (!(s->flags & SF_FINST_MASK))
s->flags |= SF_FINST_D;
return 0;
}
else if (rule->action == ACT_TCP_CLOSE) {
chn_prod(rep)->flags |= SI_FL_NOLINGER | SI_FL_NOHALF;
si_shutr(chn_prod(rep));
si_shutw(chn_prod(rep));
break;
}
else {
/* Custom keywords. */
if (!rule->action_ptr)
continue;
if (partial & SMP_OPT_FINAL)
act_flags |= ACT_FLAG_FINAL;
switch (rule->action_ptr(rule, s->be, s->sess, s, act_flags)) {
case ACT_RET_ERR:
case ACT_RET_CONT:
continue;
case ACT_RET_STOP:
break;
case ACT_RET_YIELD:
channel_dont_close(rep);
s->current_rule = rule;
return 0;
}
break; /* ACT_RET_STOP */
}
}
}
/* if we get there, it means we have no rule which matches, or
* we have an explicit accept, so we apply the default accept.
*/
rep->analysers &= ~an_bit;
rep->analyse_exp = TICK_ETERNITY;
return 1;
}
/* This function performs the TCP layer4 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 connection-based client-facing stream interfaces.
*/
int tcp_exec_l4_rules(struct session *sess)
{
struct act_rule *rule;
struct stksess *ts;
struct stktable *t = NULL;
struct connection *conn = objt_conn(sess->origin);
int result = 1;
enum acl_test_res ret;
if (!conn)
return result;
list_for_each_entry(rule, &sess->fe->tcp_req.l4_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) {
HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_conn, 1);
if (sess->listener && sess->listener->counters)
HA_ATOMIC_ADD(&sess->listener->counters->denied_conn, 1);
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[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[trk_idx(rule->action)], t, ts);
}
else if (rule->action == ACT_TCP_EXPECT_PX) {
conn->flags |= CO_FL_ACCEPT_PROXY;
conn_sock_want_recv(conn);
}
else if (rule->action == ACT_TCP_EXPECT_CIP) {
conn->flags |= CO_FL_ACCEPT_CIP;
conn_sock_want_recv(conn);
}
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 connection actions.");
case ACT_RET_STOP:
break;
case ACT_RET_CONT:
continue;
case ACT_RET_ERR:
result = 0;
break;
}
break; /* ACT_RET_STOP */
}
}
}
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) {
HA_ATOMIC_ADD(&sess->fe->fe_counters.denied_sess, 1);
if (sess->listener && sess->listener->counters)
HA_ATOMIC_ADD(&sess->listener->counters->denied_sess, 1);
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[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[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;
}
/* Parse a tcp-response rule. Return a negative value in case of failure */
static int tcp_parse_response_rule(char **args, int arg, int section_type,
struct proxy *curpx, struct proxy *defpx,
struct act_rule *rule, char **err,
unsigned int where,
const char *file, int line)
{
if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
memprintf(err, "%s %s is only allowed in 'backend' sections",
args[0], args[1]);
return -1;
}
if (strcmp(args[arg], "accept") == 0) {
arg++;
rule->action = ACT_ACTION_ALLOW;
}
else if (strcmp(args[arg], "reject") == 0) {
arg++;
rule->action = ACT_ACTION_DENY;
}
else if (strcmp(args[arg], "close") == 0) {
arg++;
rule->action = ACT_TCP_CLOSE;
}
else {
struct action_kw *kw;
kw = tcp_res_cont_action(args[arg]);
if (kw) {
arg++;
rule->from = ACT_F_TCP_RES_CNT;
rule->kw = kw;
if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
return -1;
} else {
action_build_list(&tcp_res_cont_keywords, &trash);
memprintf(err,
"'%s %s' expects 'accept', 'close', 'reject', %s in %s '%s' (got '%s')",
args[0], args[1], trash.area,
proxy_type_str(curpx), curpx->id, args[arg]);
return -1;
}
}
if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
memprintf(err,
"'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
return -1;
}
}
else if (*args[arg]) {
memprintf(err,
"'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
return -1;
}
return 0;
}
/* Parse a tcp-request rule. Return a negative value in case of failure */
static int tcp_parse_request_rule(char **args, int arg, int section_type,
struct proxy *curpx, struct proxy *defpx,
struct act_rule *rule, char **err,
unsigned int where, const char *file, int line)
{
if (curpx == defpx) {
memprintf(err, "%s %s is not allowed in 'defaults' sections",
args[0], args[1]);
return -1;
}
if (!strcmp(args[arg], "accept")) {
arg++;
rule->action = ACT_ACTION_ALLOW;
}
else if (!strcmp(args[arg], "reject")) {
arg++;
rule->action = ACT_ACTION_DENY;
}
else if (strcmp(args[arg], "capture") == 0) {
struct sample_expr *expr;
struct cap_hdr *hdr;
int kw = arg;
int len = 0;
if (!(curpx->cap & PR_CAP_FE)) {
memprintf(err,
"'%s %s %s' : proxy '%s' has no frontend capability",
args[0], args[1], args[kw], curpx->id);
return -1;
}
if (!(where & SMP_VAL_FE_REQ_CNT)) {
memprintf(err,
"'%s %s' is not allowed in '%s %s' rules in %s '%s'",
args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
return -1;
}
arg++;
curpx->conf.args.ctx = ARGC_CAP;
expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
if (!expr) {
memprintf(err,
"'%s %s %s' : %s",
args[0], args[1], args[kw], *err);
return -1;
}
if (!(expr->fetch->val & where)) {
memprintf(err,
"'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here",
args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use));
free(expr);
return -1;
}
if (strcmp(args[arg], "len") == 0) {
arg++;
if (!args[arg]) {
memprintf(err,
"'%s %s %s' : missing length value",
args[0], args[1], args[kw]);
free(expr);
return -1;
}
/* we copy the table name for now, it will be resolved later */
len = atoi(args[arg]);
if (len <= 0) {
memprintf(err,
"'%s %s %s' : length must be > 0",
args[0], args[1], args[kw]);
free(expr);
return -1;
}
arg++;
}
if (!len) {
memprintf(err,
"'%s %s %s' : a positive 'len' argument is mandatory",
args[0], args[1], args[kw]);
free(expr);
return -1;
}
hdr = calloc(1, sizeof(*hdr));
hdr->next = curpx->req_cap;
hdr->name = NULL; /* not a header capture */
hdr->namelen = 0;
hdr->len = len;
hdr->pool = create_pool("caphdr", hdr->len + 1, MEM_F_SHARED);
hdr->index = curpx->nb_req_cap++;
curpx->req_cap = hdr;
curpx->to_log |= LW_REQHDR;
/* check if we need to allocate an hdr_idx struct for HTTP parsing */
curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
rule->arg.cap.expr = expr;
rule->arg.cap.hdr = hdr;
rule->action = ACT_TCP_CAPTURE;
}
else if (strncmp(args[arg], "track-sc", 8) == 0) {
struct sample_expr *expr;
int kw = arg;
unsigned int tsc_num;
const char *tsc_num_str;
arg++;
tsc_num_str = &args[kw][8];
if (cfg_parse_track_sc_num(&tsc_num, tsc_num_str, tsc_num_str + strlen(tsc_num_str), err) == -1) {
memprintf(err, "'%s %s %s' : %s", args[0], args[1], args[kw], *err);
return -1;
}
curpx->conf.args.ctx = ARGC_TRK;
expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args);
if (!expr) {
memprintf(err,
"'%s %s %s' : %s",
args[0], args[1], args[kw], *err);
return -1;
}
if (!(expr->fetch->val & where)) {
memprintf(err,
"'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here",
args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use));
free(expr);
return -1;
}
/* check if we need to allocate an hdr_idx struct for HTTP parsing */
curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY);
if (strcmp(args[arg], "table") == 0) {
arg++;
if (!args[arg]) {
memprintf(err,
"'%s %s %s' : missing table name",
args[0], args[1], args[kw]);
free(expr);
return -1;
}
/* we copy the table name for now, it will be resolved later */
rule->arg.trk_ctr.table.n = strdup(args[arg]);
arg++;
}
rule->arg.trk_ctr.expr = expr;
rule->action = ACT_ACTION_TRK_SC0 + tsc_num;
rule->check_ptr = check_trk_action;
}
else if (strcmp(args[arg], "expect-proxy") == 0) {
if (strcmp(args[arg+1], "layer4") != 0) {
memprintf(err,
"'%s %s %s' only supports 'layer4' in %s '%s' (got '%s')",
args[0], args[1], args[arg], proxy_type_str(curpx), curpx->id, args[arg+1]);
return -1;
}
if (!(where & SMP_VAL_FE_CON_ACC)) {
memprintf(err,
"'%s %s' is not allowed in '%s %s' rules in %s '%s'",
args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
return -1;
}
arg += 2;
rule->action = ACT_TCP_EXPECT_PX;
}
else if (strcmp(args[arg], "expect-netscaler-cip") == 0) {
if (strcmp(args[arg+1], "layer4") != 0) {
memprintf(err,
"'%s %s %s' only supports 'layer4' in %s '%s' (got '%s')",
args[0], args[1], args[arg], proxy_type_str(curpx), curpx->id, args[arg+1]);
return -1;
}
if (!(where & SMP_VAL_FE_CON_ACC)) {
memprintf(err,
"'%s %s' is not allowed in '%s %s' rules in %s '%s'",
args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id);
return -1;
}
arg += 2;
rule->action = ACT_TCP_EXPECT_CIP;
}
else {
struct action_kw *kw;
if (where & SMP_VAL_FE_CON_ACC) {
/* L4 */
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]);
rule->kw = kw;
rule->from = ACT_F_TCP_REQ_CNT;
}
if (kw) {
arg++;
if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
return -1;
} 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,
"'%s %s' expects 'accept', 'reject', 'track-sc0' ... 'track-sc%d', %s "
"in %s '%s' (got '%s').\n",
args[0], args[1], MAX_SESS_STKCTR-1,
trash.area, proxy_type_str(curpx),
curpx->id, args[arg]);
return -1;
}
}
if (strcmp(args[arg], "if") == 0 || strcmp(args[arg], "unless") == 0) {
if ((rule->cond = build_acl_cond(file, line, &curpx->acl, curpx, (const char **)args+arg, err)) == NULL) {
memprintf(err,
"'%s %s %s' : error detected in %s '%s' while parsing '%s' condition : %s",
args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg], *err);
return -1;
}
}
else if (*args[arg]) {
memprintf(err,
"'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (got '%s')",
args[0], args[1], args[2], proxy_type_str(curpx), curpx->id, args[arg]);
return -1;
}
return 0;
}
/* This function should be called to parse a line starting with the "tcp-response"
* keyword.
*/
static int tcp_parse_tcp_rep(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **err)
{
const char *ptr = NULL;
unsigned int val;
int warn = 0;
int arg;
struct act_rule *rule;
unsigned int where;
const struct acl *acl;
const char *kw;
if (!*args[1]) {
memprintf(err, "missing argument for '%s' in %s '%s'",
args[0], proxy_type_str(curpx), curpx->id);
return -1;
}
if (strcmp(args[1], "inspect-delay") == 0) {
if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
memprintf(err, "%s %s is only allowed in 'backend' sections",
args[0], args[1]);
return -1;
}
if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
memprintf(err,
"'%s %s' expects a positive delay in milliseconds, in %s '%s'",
args[0], args[1], proxy_type_str(curpx), curpx->id);
if (ptr)
memprintf(err, "%s (unexpected character '%c')", *err, *ptr);
return -1;
}
if (curpx->tcp_rep.inspect_delay) {
memprintf(err, "ignoring %s %s (was already defined) in %s '%s'",
args[0], args[1], proxy_type_str(curpx), curpx->id);
return 1;
}
curpx->tcp_rep.inspect_delay = val;
return 0;
}
rule = calloc(1, sizeof(*rule));
LIST_INIT(&rule->list);
arg = 1;
where = 0;
if (strcmp(args[1], "content") == 0) {
arg++;
if (curpx->cap & PR_CAP_FE)
where |= SMP_VAL_FE_RES_CNT;
if (curpx->cap & PR_CAP_BE)
where |= SMP_VAL_BE_RES_CNT;
if (tcp_parse_response_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++;
}
LIST_ADDQ(&curpx->tcp_rep.inspect_rules, &rule->list);
}
else {
memprintf(err,
"'%s' expects 'inspect-delay' or 'content' in %s '%s' (got '%s')",
args[0], proxy_type_str(curpx), curpx->id, args[1]);
goto error;
}
return warn;
error:
free(rule);
return -1;
}
/* This function should be called to parse a line starting with the "tcp-request"
* keyword.
*/
static int tcp_parse_tcp_req(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file, int line,
char **err)
{
const char *ptr = NULL;
unsigned int val;
int warn = 0;
int arg;
struct act_rule *rule;
unsigned int where;
const struct acl *acl;
const char *kw;
if (!*args[1]) {
if (curpx == defpx)
memprintf(err, "missing argument for '%s' in defaults section", args[0]);
else
memprintf(err, "missing argument for '%s' in %s '%s'",
args[0], proxy_type_str(curpx), curpx->id);
return -1;
}
if (!strcmp(args[1], "inspect-delay")) {
if (curpx == defpx) {
memprintf(err, "%s %s is not allowed in 'defaults' sections",
args[0], args[1]);
return -1;
}
if (!*args[2] || (ptr = parse_time_err(args[2], &val, TIME_UNIT_MS))) {
memprintf(err,
"'%s %s' expects a positive delay in milliseconds, in %s '%s'",
args[0], args[1], proxy_type_str(curpx), curpx->id);
if (ptr)
memprintf(err, "%s (unexpected character '%c')", *err, *ptr);
return -1;
}
if (curpx->tcp_req.inspect_delay) {
memprintf(err, "ignoring %s %s (was already defined) in %s '%s'",
args[0], args[1], proxy_type_str(curpx), curpx->id);
return 1;
}
curpx->tcp_req.inspect_delay = val;
return 0;
}
rule = calloc(1, sizeof(*rule));
LIST_INIT(&rule->list);
arg = 1;
where = 0;
if (strcmp(args[1], "content") == 0) {
arg++;
if (curpx->cap & PR_CAP_FE)
where |= SMP_VAL_FE_REQ_CNT;
if (curpx->cap & PR_CAP_BE)
where |= SMP_VAL_BE_REQ_CNT;
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_cont(curpx, file, line, args[0]);
LIST_ADDQ(&curpx->tcp_req.inspect_rules, &rule->list);
}
else if (strcmp(args[1], "connection") == 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_CON_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_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,
"'%s' expects 'inspect-delay', 'connection', or 'content' in defaults section (got '%s')",
args[0], args[1]);
else
memprintf(err,
"'%s' expects 'inspect-delay', 'connection', or 'content' in %s '%s' (got '%s')",
args[0], proxy_type_str(curpx), curpx->id, args[1]);
goto error;
}
return warn;
error:
free(rule);
return -1;
}
static struct cfg_kw_list cfg_kws = {ILH, {
{ CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
{ CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
{ 0, NULL, NULL },
}};
__attribute__((constructor))
static void __tcp_protocol_init(void)
{
cfg_register_keywords(&cfg_kws);
}
/*
* Local variables:
* c-indent-level: 8
* c-basic-offset: 8
* End:
*/