mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-04-18 04:55:37 +00:00
When a TCP content ruleset is evaluated, we stop waiting for more data if the inspect-delay is reached, if there is a read error or if we know no more data will be received. This last point is only valid for ACLs. An action may decide to yield for another reason. For instance, in the SPOE, the "send-spoe-group" action yields while the agent response is not received. Thus, now, an action call is final only when the inspect-delay is reached or if there is a read error. But it is possible for an action to yield if the buffer is full or if CF_EOI flag is set. This patch could be backported to all supported versions.
1429 lines
44 KiB
C
1429 lines
44 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 <haproxy/acl.h>
|
|
#include <haproxy/action.h>
|
|
#include <haproxy/api.h>
|
|
#include <haproxy/arg-t.h>
|
|
#include <haproxy/capture-t.h>
|
|
#include <haproxy/cfgparse.h>
|
|
#include <haproxy/channel.h>
|
|
#include <haproxy/connection.h>
|
|
#include <haproxy/global.h>
|
|
#include <haproxy/list.h>
|
|
#include <haproxy/log.h>
|
|
#include <haproxy/proxy.h>
|
|
#include <haproxy/sample.h>
|
|
#include <haproxy/sc_strm.h>
|
|
#include <haproxy/stconn.h>
|
|
#include <haproxy/stick_table.h>
|
|
#include <haproxy/stream-t.h>
|
|
#include <haproxy/tcp_rules.h>
|
|
#include <haproxy/ticks.h>
|
|
#include <haproxy/tools.h>
|
|
#include <haproxy/trace.h>
|
|
|
|
|
|
#define TRACE_SOURCE &trace_strm
|
|
|
|
/* 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_APPEND(&tcp_req_conn_keywords, &kw_list->list);
|
|
}
|
|
|
|
void tcp_req_sess_keywords_register(struct action_kw_list *kw_list)
|
|
{
|
|
LIST_APPEND(&tcp_req_sess_keywords, &kw_list->list);
|
|
}
|
|
|
|
void tcp_req_cont_keywords_register(struct action_kw_list *kw_list)
|
|
{
|
|
LIST_APPEND(&tcp_req_cont_keywords, &kw_list->list);
|
|
}
|
|
|
|
void tcp_res_cont_keywords_register(struct action_kw_list *kw_list)
|
|
{
|
|
LIST_APPEND(&tcp_res_cont_keywords, &kw_list->list);
|
|
}
|
|
|
|
/*
|
|
* Return the struct tcp_req_action_kw associated to a keyword.
|
|
*/
|
|
struct action_kw *tcp_req_conn_action(const char *kw)
|
|
{
|
|
return action_lookup(&tcp_req_conn_keywords, kw);
|
|
}
|
|
|
|
struct action_kw *tcp_req_sess_action(const char *kw)
|
|
{
|
|
return action_lookup(&tcp_req_sess_keywords, kw);
|
|
}
|
|
|
|
struct action_kw *tcp_req_cont_action(const char *kw)
|
|
{
|
|
return action_lookup(&tcp_req_cont_keywords, kw);
|
|
}
|
|
|
|
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 list *def_rules, *rules;
|
|
struct session *sess = s->sess;
|
|
struct act_rule *rule;
|
|
int partial;
|
|
int act_opts = 0;
|
|
|
|
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s);
|
|
|
|
def_rules = ((s->be->defpx && (an_bit == AN_REQ_INSPECT_FE || s->be->defpx != sess->fe->defpx)) ? &s->be->defpx->tcp_req.inspect_rules : NULL);
|
|
rules = &s->be->tcp_req.inspect_rules;
|
|
|
|
/* 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_EOI|CF_SHUTR|CF_READ_ERROR)) || channel_full(req, global.tune.maxrewrite) ||
|
|
sc_waiting_room(chn_prod(req)) ||
|
|
!s->be->tcp_req.inspect_delay || tick_is_expired(s->rules_exp, now_ms)) {
|
|
partial = SMP_OPT_FINAL;
|
|
/* Action may yield while the inspect_delay is not expired and there is no read error */
|
|
if ((req->flags & CF_READ_ERROR) || !s->be->tcp_req.inspect_delay || tick_is_expired(s->rules_exp, now_ms))
|
|
act_opts |= ACT_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 ((def_rules && s->current_rule_list == def_rules) || s->current_rule_list == rules)
|
|
goto resume_execution;
|
|
}
|
|
s->current_rule_list = ((!def_rules || s->current_rule_list == def_rules) ? rules : def_rules);
|
|
|
|
restart:
|
|
list_for_each_entry(rule, s->current_rule_list, 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_opts |= ACT_OPT_FIRST;
|
|
resume_execution:
|
|
/* Always call the action function if defined */
|
|
if (rule->action_ptr) {
|
|
switch (rule->action_ptr(rule, s->be, s->sess, s, act_opts)) {
|
|
case ACT_RET_CONT:
|
|
break;
|
|
case ACT_RET_STOP:
|
|
case ACT_RET_DONE:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto end;
|
|
case ACT_RET_YIELD:
|
|
s->current_rule = rule;
|
|
if (act_opts & ACT_OPT_FINAL) {
|
|
send_log(s->be, LOG_WARNING,
|
|
"Internal error: yield not allowed if the inspect-delay expired "
|
|
"for the tcp-request content actions.");
|
|
goto internal;
|
|
}
|
|
goto missing_data;
|
|
case ACT_RET_DENY:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto deny;
|
|
case ACT_RET_ABRT:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto abort;
|
|
case ACT_RET_ERR:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto internal;
|
|
case ACT_RET_INV:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto invalid;
|
|
}
|
|
continue; /* eval the next rule */
|
|
}
|
|
|
|
/* If not action function defined, check for known actions */
|
|
if (rule->action == ACT_ACTION_ALLOW) {
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto end;
|
|
}
|
|
else if (rule->action == ACT_ACTION_DENY) {
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto deny;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (def_rules && s->current_rule_list == def_rules) {
|
|
s->current_rule_list = rules;
|
|
goto restart;
|
|
}
|
|
|
|
end:
|
|
/* 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;
|
|
s->current_rule = s->current_rule_list = NULL;
|
|
req->analyse_exp = s->rules_exp = TICK_ETERNITY;
|
|
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s);
|
|
return 1;
|
|
|
|
missing_data:
|
|
channel_dont_connect(req);
|
|
/* just set the request timeout once at the beginning of the request */
|
|
if (!tick_isset(s->rules_exp) && s->be->tcp_req.inspect_delay)
|
|
s->rules_exp = tick_add(now_ms, s->be->tcp_req.inspect_delay);
|
|
req->analyse_exp = tick_first((tick_is_expired(req->analyse_exp, now_ms) ? 0 : req->analyse_exp), s->rules_exp);
|
|
DBG_TRACE_DEVEL("waiting for more data", STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s);
|
|
return 0;
|
|
|
|
deny:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_req);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->denied_req);
|
|
goto reject;
|
|
|
|
internal:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.internal_errors);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->internal_errors);
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_INTERNAL;
|
|
goto reject;
|
|
|
|
invalid:
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.failed_req);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->failed_req);
|
|
|
|
reject:
|
|
sc_must_kill_conn(chn_prod(req));
|
|
channel_abort(req);
|
|
channel_abort(&s->res);
|
|
|
|
abort:
|
|
req->analysers &= AN_REQ_FLT_END;
|
|
s->current_rule = s->current_rule_list = NULL;
|
|
req->analyse_exp = s->rules_exp = TICK_ETERNITY;
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
s->flags |= SF_FINST_R;
|
|
DBG_TRACE_DEVEL("leaving on error|deny|abort", STRM_EV_STRM_ANA|STRM_EV_TCP_ANA|STRM_EV_TCP_ERR, s);
|
|
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 list *def_rules, *rules;
|
|
struct session *sess = s->sess;
|
|
struct act_rule *rule;
|
|
int partial;
|
|
int act_opts = 0;
|
|
|
|
DBG_TRACE_ENTER(STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s);
|
|
|
|
def_rules = (s->be->defpx ? &s->be->defpx->tcp_rep.inspect_rules : NULL);
|
|
rules = &s->be->tcp_rep.inspect_rules;
|
|
|
|
/* 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_EOI|CF_SHUTR|CF_READ_ERROR)) || channel_full(rep, global.tune.maxrewrite) ||
|
|
sc_waiting_room(chn_prod(rep)) ||
|
|
!s->be->tcp_rep.inspect_delay || tick_is_expired(s->rules_exp, now_ms)) {
|
|
partial = SMP_OPT_FINAL;
|
|
/* Action may yield while the inspect_delay is not expired and there is no read error */
|
|
if ((rep->flags & CF_READ_ERROR) || !s->be->tcp_rep.inspect_delay || tick_is_expired(s->rules_exp, now_ms))
|
|
act_opts |= ACT_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 ((def_rules && s->current_rule_list == def_rules) || s->current_rule_list == rules)
|
|
goto resume_execution;
|
|
}
|
|
s->current_rule_list = ((!def_rules || s->current_rule_list == def_rules) ? rules : def_rules);
|
|
|
|
restart:
|
|
list_for_each_entry(rule, s->current_rule_list, 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)
|
|
goto missing_data;
|
|
|
|
ret = acl_pass(ret);
|
|
if (rule->cond->pol == ACL_COND_UNLESS)
|
|
ret = !ret;
|
|
}
|
|
|
|
if (ret) {
|
|
act_opts |= ACT_OPT_FIRST;
|
|
resume_execution:
|
|
/* Always call the action function if defined */
|
|
if (rule->action_ptr) {
|
|
switch (rule->action_ptr(rule, s->be, s->sess, s, act_opts)) {
|
|
case ACT_RET_CONT:
|
|
break;
|
|
case ACT_RET_STOP:
|
|
case ACT_RET_DONE:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto end;
|
|
case ACT_RET_YIELD:
|
|
s->current_rule = rule;
|
|
if (act_opts & ACT_OPT_FINAL) {
|
|
send_log(s->be, LOG_WARNING,
|
|
"Internal error: yield not allowed if the inspect-delay expired "
|
|
"for the tcp-response content actions.");
|
|
goto internal;
|
|
}
|
|
channel_dont_close(rep);
|
|
goto missing_data;
|
|
case ACT_RET_DENY:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto deny;
|
|
case ACT_RET_ABRT:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto abort;
|
|
case ACT_RET_ERR:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto internal;
|
|
case ACT_RET_INV:
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto invalid;
|
|
}
|
|
continue; /* eval the next rule */
|
|
}
|
|
|
|
/* If not action function defined, check for known actions */
|
|
if (rule->action == ACT_ACTION_ALLOW) {
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto end;
|
|
}
|
|
else if (rule->action == ACT_ACTION_DENY) {
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto deny;
|
|
}
|
|
else if (rule->action == ACT_TCP_CLOSE) {
|
|
chn_prod(rep)->flags |= SC_FL_NOLINGER | SC_FL_NOHALF;
|
|
sc_must_kill_conn(chn_prod(rep));
|
|
sc_shutr(chn_prod(rep));
|
|
sc_shutw(chn_prod(rep));
|
|
s->last_rule_file = rule->conf.file;
|
|
s->last_rule_line = rule->conf.line;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (def_rules && s->current_rule_list == def_rules) {
|
|
s->current_rule_list = rules;
|
|
goto restart;
|
|
}
|
|
|
|
end:
|
|
/* 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;
|
|
s->current_rule = s->current_rule_list = NULL;
|
|
rep->analyse_exp = s->rules_exp = TICK_ETERNITY;
|
|
DBG_TRACE_LEAVE(STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s);
|
|
return 1;
|
|
|
|
missing_data:
|
|
/* just set the analyser timeout once at the beginning of the response */
|
|
if (!tick_isset(s->rules_exp) && s->be->tcp_rep.inspect_delay)
|
|
s->rules_exp = tick_add(now_ms, s->be->tcp_rep.inspect_delay);
|
|
rep->analyse_exp = tick_first((tick_is_expired(rep->analyse_exp, now_ms) ? 0 : rep->analyse_exp), s->rules_exp);
|
|
DBG_TRACE_DEVEL("waiting for more data", STRM_EV_STRM_ANA|STRM_EV_TCP_ANA, s);
|
|
return 0;
|
|
|
|
deny:
|
|
_HA_ATOMIC_INC(&s->sess->fe->fe_counters.denied_resp);
|
|
_HA_ATOMIC_INC(&s->be->be_counters.denied_resp);
|
|
if (s->sess->listener && s->sess->listener->counters)
|
|
_HA_ATOMIC_INC(&s->sess->listener->counters->denied_resp);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.denied_resp);
|
|
goto reject;
|
|
|
|
internal:
|
|
_HA_ATOMIC_INC(&s->sess->fe->fe_counters.internal_errors);
|
|
_HA_ATOMIC_INC(&s->be->be_counters.internal_errors);
|
|
if (s->sess->listener && s->sess->listener->counters)
|
|
_HA_ATOMIC_INC(&s->sess->listener->counters->internal_errors);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.internal_errors);
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_INTERNAL;
|
|
goto reject;
|
|
|
|
invalid:
|
|
_HA_ATOMIC_INC(&s->be->be_counters.failed_resp);
|
|
if (objt_server(s->target))
|
|
_HA_ATOMIC_INC(&__objt_server(s->target)->counters.failed_resp);
|
|
|
|
reject:
|
|
sc_must_kill_conn(chn_prod(rep));
|
|
channel_abort(rep);
|
|
channel_abort(&s->req);
|
|
|
|
abort:
|
|
rep->analysers &= AN_RES_FLT_END;
|
|
s->current_rule = s->current_rule_list = NULL;
|
|
rep->analyse_exp = s->rules_exp = TICK_ETERNITY;
|
|
|
|
if (!(s->flags & SF_ERR_MASK))
|
|
s->flags |= SF_ERR_PRXCOND;
|
|
if (!(s->flags & SF_FINST_MASK))
|
|
s->flags |= SF_FINST_D;
|
|
DBG_TRACE_DEVEL("leaving on error", STRM_EV_STRM_ANA|STRM_EV_TCP_ANA|STRM_EV_TCP_ERR, s);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* 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 connectors.
|
|
*/
|
|
int tcp_exec_l4_rules(struct session *sess)
|
|
{
|
|
struct proxy *px = sess->fe;
|
|
struct act_rule *rule;
|
|
struct connection *conn = objt_conn(sess->origin);
|
|
int result = 1;
|
|
enum acl_test_res ret;
|
|
|
|
if (!conn)
|
|
return result;
|
|
|
|
if (sess->fe->defpx)
|
|
px = sess->fe->defpx;
|
|
|
|
restart:
|
|
list_for_each_entry(rule, &px->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) {
|
|
/* Always call the action function if defined */
|
|
if (rule->action_ptr) {
|
|
switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_OPT_FINAL | ACT_OPT_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.");
|
|
/* fall through */
|
|
case ACT_RET_STOP:
|
|
case ACT_RET_DONE:
|
|
goto end;
|
|
case ACT_RET_CONT:
|
|
break;
|
|
case ACT_RET_DENY:
|
|
case ACT_RET_ABRT:
|
|
case ACT_RET_ERR:
|
|
case ACT_RET_INV:
|
|
result = 0;
|
|
goto end;
|
|
}
|
|
continue; /* eval the next rule */
|
|
}
|
|
|
|
/* If not action function defined, check for known actions */
|
|
if (rule->action == ACT_ACTION_ALLOW) {
|
|
goto end;
|
|
}
|
|
else if (rule->action == ACT_ACTION_DENY) {
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_conn);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->denied_conn);
|
|
|
|
result = 0;
|
|
goto end;
|
|
}
|
|
else if (rule->action == ACT_TCP_EXPECT_PX) {
|
|
if (!(conn->flags & CO_FL_HANDSHAKE)) {
|
|
if (xprt_add_hs(conn) < 0) {
|
|
result = 0;
|
|
goto end;
|
|
}
|
|
}
|
|
conn->flags |= CO_FL_ACCEPT_PROXY;
|
|
}
|
|
else if (rule->action == ACT_TCP_EXPECT_CIP) {
|
|
if (!(conn->flags & CO_FL_HANDSHAKE)) {
|
|
if (xprt_add_hs(conn) < 0) {
|
|
result = 0;
|
|
goto end;
|
|
}
|
|
}
|
|
conn->flags |= CO_FL_ACCEPT_CIP;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (px != sess->fe) {
|
|
px = sess->fe;
|
|
goto restart;
|
|
}
|
|
end:
|
|
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 connectors.
|
|
* 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 proxy *px = sess->fe;
|
|
struct act_rule *rule;
|
|
int result = 1;
|
|
enum acl_test_res ret;
|
|
|
|
if (sess->fe->defpx)
|
|
px = sess->fe->defpx;
|
|
|
|
restart:
|
|
list_for_each_entry(rule, &px->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) {
|
|
/* Always call the action function if defined */
|
|
if (rule->action_ptr) {
|
|
switch (rule->action_ptr(rule, sess->fe, sess, NULL, ACT_OPT_FINAL | ACT_OPT_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.");
|
|
/* fall through */
|
|
case ACT_RET_STOP:
|
|
case ACT_RET_DONE:
|
|
goto end;
|
|
case ACT_RET_CONT:
|
|
break;
|
|
case ACT_RET_DENY:
|
|
case ACT_RET_ABRT:
|
|
case ACT_RET_ERR:
|
|
case ACT_RET_INV:
|
|
result = 0;
|
|
goto end;
|
|
}
|
|
continue; /* eval the next rule */
|
|
}
|
|
|
|
/* If not action function defined, check for known actions */
|
|
if (rule->action == ACT_ACTION_ALLOW) {
|
|
goto end;
|
|
}
|
|
else if (rule->action == ACT_ACTION_DENY) {
|
|
_HA_ATOMIC_INC(&sess->fe->fe_counters.denied_sess);
|
|
if (sess->listener && sess->listener->counters)
|
|
_HA_ATOMIC_INC(&sess->listener->counters->denied_sess);
|
|
|
|
result = 0;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (px != sess->fe) {
|
|
px = sess->fe;
|
|
goto restart;
|
|
}
|
|
end:
|
|
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, const struct proxy *defpx,
|
|
struct act_rule *rule, char **err,
|
|
unsigned int where,
|
|
const char *file, int line)
|
|
{
|
|
if ((curpx == defpx && strlen(defpx->id) == 0) || !(curpx->cap & PR_CAP_BE)) {
|
|
memprintf(err, "%s %s is only allowed in 'backend' sections or 'defaults' section with a name",
|
|
args[0], args[1]);
|
|
return -1;
|
|
}
|
|
|
|
if (strcmp(args[arg], "accept") == 0) {
|
|
arg++;
|
|
rule->action = ACT_ACTION_ALLOW;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
}
|
|
else if (strcmp(args[arg], "reject") == 0) {
|
|
arg++;
|
|
rule->action = ACT_ACTION_DENY;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
}
|
|
else if (strcmp(args[arg], "close") == 0) {
|
|
arg++;
|
|
rule->action = ACT_TCP_CLOSE;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
}
|
|
else {
|
|
struct action_kw *kw;
|
|
kw = tcp_res_cont_action(args[arg]);
|
|
if (kw) {
|
|
arg++;
|
|
rule->kw = kw;
|
|
if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
|
|
return -1;
|
|
} else {
|
|
const char *extra[] = { "accept", "reject", "close", NULL };
|
|
const char *best = action_suggest(args[arg], &tcp_res_cont_keywords, extra);
|
|
|
|
action_build_list(&tcp_res_cont_keywords, &trash);
|
|
memprintf(err,
|
|
"'%s %s' expects 'accept', 'close', 'reject', %s in %s '%s' (got '%s').%s%s%s",
|
|
args[0], args[1], trash.area,
|
|
proxy_type_str(curpx), curpx->id, args[arg],
|
|
best ? " Did you mean '" : "",
|
|
best ? best : "",
|
|
best ? "' maybe ?" : "");
|
|
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 executes a track-sc* actions. On success, it returns
|
|
* ACT_RET_CONT. If it must yield, it return ACT_RET_YIELD. Otherwsize
|
|
* ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return tcp_action_track_sc(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct stksess *ts;
|
|
struct stktable *t;
|
|
struct stktable_key *key;
|
|
struct sample smp;
|
|
int opt;
|
|
|
|
opt = SMP_OPT_DIR_REQ;
|
|
if (flags & ACT_FLAG_FINAL)
|
|
opt |= SMP_OPT_FINAL;
|
|
|
|
t = rule->arg.trk_ctr.table.t;
|
|
if (rule->from == ACT_F_TCP_REQ_CNT) { /* L7 rules: use the stream */
|
|
if (stkctr_entry(&s->stkctr[rule->action]))
|
|
goto end;
|
|
|
|
key = stktable_fetch_key(t, s->be, sess, s, opt, rule->arg.trk_ctr.expr, &smp);
|
|
|
|
if ((smp.flags & SMP_F_MAY_CHANGE) && !(flags & ACT_FLAG_FINAL))
|
|
return ACT_RET_YIELD; /* key might appear later */
|
|
|
|
if (key && (ts = stktable_get_entry(t, key))) {
|
|
stream_track_stkctr(&s->stkctr[rule->action], t, ts);
|
|
stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_CONTENT);
|
|
if (sess->fe != s->be)
|
|
stkctr_set_flags(&s->stkctr[rule->action], STKCTR_TRACK_BACKEND);
|
|
}
|
|
}
|
|
else { /* L4/L5 rules: use the session */
|
|
if (stkctr_entry(&sess->stkctr[rule->action]))
|
|
goto end;
|
|
|
|
key = stktable_fetch_key(t, sess->fe, sess, NULL, opt, rule->arg.trk_ctr.expr, NULL);
|
|
if (key && (ts = stktable_get_entry(t, key)))
|
|
stream_track_stkctr(&sess->stkctr[rule->action], t, ts);
|
|
}
|
|
|
|
end:
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
/* This function executes a capture actions. It executes a fetch expression,
|
|
* turns the result into a string and puts it in a capture slot. On success, it
|
|
* returns ACT_RET_CONT. If it must yield, it return ACT_RET_YIELD. Otherwsize
|
|
* ACT_RET_ERR is returned.
|
|
*/
|
|
static enum act_return tcp_action_capture(struct act_rule *rule, struct proxy *px,
|
|
struct session *sess, struct stream *s, int flags)
|
|
{
|
|
struct sample *key;
|
|
struct cap_hdr *h = rule->arg.cap.hdr;
|
|
char **cap = s->req_cap;
|
|
int len, opt;
|
|
|
|
opt = ((rule->from == ACT_F_TCP_REQ_CNT) ? SMP_OPT_DIR_REQ : SMP_OPT_DIR_RES);
|
|
if (flags & ACT_FLAG_FINAL)
|
|
opt |= SMP_OPT_FINAL;
|
|
|
|
key = sample_fetch_as_type(s->be, sess, s, opt, rule->arg.cap.expr, SMP_T_STR);
|
|
if (!key)
|
|
goto end;
|
|
|
|
if ((key->flags & SMP_F_MAY_CHANGE) && !(flags & ACT_FLAG_FINAL))
|
|
return ACT_RET_YIELD; /* key might appear later */
|
|
|
|
if (cap[h->index] == NULL) {
|
|
cap[h->index] = pool_alloc(h->pool);
|
|
if (cap[h->index] == NULL) /* no more capture memory, ignore error */
|
|
goto end;
|
|
}
|
|
|
|
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;
|
|
|
|
end:
|
|
return ACT_RET_CONT;
|
|
}
|
|
|
|
static void release_tcp_capture(struct act_rule * rule)
|
|
{
|
|
release_sample_expr(rule->arg.cap.expr);
|
|
}
|
|
|
|
|
|
static void release_tcp_track_sc(struct act_rule * rule)
|
|
{
|
|
release_sample_expr(rule->arg.trk_ctr.expr);
|
|
}
|
|
|
|
/* 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, const struct proxy *defpx,
|
|
struct act_rule *rule, char **err,
|
|
unsigned int where, const char *file, int line)
|
|
{
|
|
if (curpx == defpx && strlen(defpx->id) == 0) {
|
|
memprintf(err, "%s %s is not allowed in anonymous 'defaults' sections",
|
|
args[0], args[1]);
|
|
return -1;
|
|
}
|
|
|
|
if (strcmp(args[arg], "accept") == 0) {
|
|
arg++;
|
|
rule->action = ACT_ACTION_ALLOW;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
}
|
|
else if (strcmp(args[arg], "reject") == 0) {
|
|
arg++;
|
|
rule->action = ACT_ACTION_DENY;
|
|
rule->flags |= ACT_FLAG_FINAL;
|
|
}
|
|
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, NULL);
|
|
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));
|
|
release_sample_expr(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]);
|
|
release_sample_expr(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]);
|
|
release_sample_expr(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));
|
|
if (!hdr) {
|
|
memprintf(err, "parsing [%s:%d] : out of memory", file, line);
|
|
release_sample_expr(expr);
|
|
return -1;
|
|
}
|
|
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 http_txn 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_CUSTOM;
|
|
rule->action_ptr = tcp_action_capture;
|
|
rule->check_ptr = check_capture;
|
|
rule->release_ptr = release_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, NULL);
|
|
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));
|
|
release_sample_expr(expr);
|
|
return -1;
|
|
}
|
|
|
|
/* check if we need to allocate an http_txn 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]);
|
|
release_sample_expr(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->action = tsc_num;
|
|
rule->arg.trk_ctr.expr = expr;
|
|
rule->action_ptr = tcp_action_track_sc;
|
|
rule->check_ptr = check_trk_action;
|
|
rule->release_ptr = release_tcp_track_sc;
|
|
}
|
|
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;
|
|
} else if (where & SMP_VAL_FE_SES_ACC) {
|
|
/* L5 */
|
|
kw = tcp_req_sess_action(args[arg]);
|
|
rule->kw = kw;
|
|
} else {
|
|
/* L6 */
|
|
kw = tcp_req_cont_action(args[arg]);
|
|
rule->kw = kw;
|
|
}
|
|
if (kw) {
|
|
arg++;
|
|
if (kw->parse((const char **)args, &arg, curpx, rule, err) == ACT_RET_PRS_ERR)
|
|
return -1;
|
|
} else {
|
|
const char *extra[] = { "accept", "reject", "capture", "track-sc", "expect-proxy", "expect-netscaler-cip", NULL };
|
|
const char *best = NULL;
|
|
|
|
|
|
if (where & SMP_VAL_FE_CON_ACC) {
|
|
action_build_list(&tcp_req_conn_keywords, &trash);
|
|
best = action_suggest(args[arg], &tcp_req_conn_keywords, extra);
|
|
}
|
|
else if (where & SMP_VAL_FE_SES_ACC) {
|
|
action_build_list(&tcp_req_sess_keywords, &trash);
|
|
best = action_suggest(args[arg], &tcp_req_sess_keywords, extra);
|
|
}
|
|
else {
|
|
action_build_list(&tcp_req_cont_keywords, &trash);
|
|
best = action_suggest(args[arg], &tcp_req_cont_keywords, extra);
|
|
}
|
|
|
|
memprintf(err,
|
|
"'%s %s' expects 'accept', 'reject', 'capture', 'expect-proxy', 'expect-netscaler-cip', 'track-sc0' ... 'track-sc%d', %s "
|
|
"in %s '%s' (got '%s').%s%s%s\n",
|
|
args[0], args[1], MAX_SESS_STKCTR-1,
|
|
trash.area, proxy_type_str(curpx),
|
|
curpx->id, args[arg],
|
|
best ? " Did you mean '" : "",
|
|
best ? best : "",
|
|
best ? "' maybe ?" : "");
|
|
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,
|
|
const 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 && strlen(defpx->id) == 0) || !(curpx->cap & PR_CAP_BE)) {
|
|
memprintf(err, "%s %s is only allowed in 'backend' sections or 'defaults' section with a name",
|
|
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 == PARSE_TIME_OVER)
|
|
memprintf(err, "%s (timer overflow in '%s', maximum value is 2147483647 ms or ~24.8 days)", *err, args[2]);
|
|
else if (ptr == PARSE_TIME_UNDER)
|
|
memprintf(err, "%s (timer underflow in '%s', minimum non-null value is 1 ms)", *err, args[2]);
|
|
else 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 = new_act_rule(ACT_F_TCP_RES_CNT, file, line);
|
|
if (!rule) {
|
|
memprintf(err, "parsing [%s:%d] : out of memory", file, line);
|
|
return -1;
|
|
}
|
|
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_APPEND(&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_act_rule(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,
|
|
const 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") == 0) {
|
|
if (curpx == defpx && strlen(defpx->id) == 0) {
|
|
memprintf(err, "%s %s is not allowed in anonymous '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 == PARSE_TIME_OVER)
|
|
memprintf(err, "%s (timer overflow in '%s', maximum value is 2147483647 ms or ~24.8 days)", *err, args[2]);
|
|
else if (ptr == PARSE_TIME_UNDER)
|
|
memprintf(err, "%s (timer underflow in '%s', minimum non-null value is 1 ms)", *err, args[2]);
|
|
else 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 = new_act_rule(0, file, line);
|
|
if (!rule) {
|
|
memprintf(err, "parsing [%s:%d] : out of memory", file, line);
|
|
return -1;
|
|
}
|
|
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;
|
|
rule->from = ACT_F_TCP_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_APPEND(&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;
|
|
rule->from = ACT_F_TCP_REQ_CON;
|
|
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_APPEND(&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;
|
|
rule->from = ACT_F_TCP_REQ_SES;
|
|
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_APPEND(&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_act_rule(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 },
|
|
}};
|
|
|
|
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-indent-level: 8
|
|
* c-basic-offset: 8
|
|
* End:
|
|
*/
|