[MEDIUM] Implement tcp inspect response rules

This commit is contained in:
Emeric Brun 2010-09-23 17:56:44 +02:00 committed by Willy Tarreau
parent c89a57284a
commit 97679e7901
5 changed files with 235 additions and 2 deletions

View File

@ -34,6 +34,7 @@ int tcpv4_connect_server(struct stream_interface *si,
struct proxy *be, struct server *srv,
struct sockaddr *srv_addr, struct sockaddr *from_addr);
int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit);
int tcp_inspect_response(struct session *s, struct buffer *rep, int an_bit);
int tcp_persist_rdp_cookie(struct session *s, struct buffer *req, int an_bit);
int tcp_exec_req_rules(struct session *s);

View File

@ -194,6 +194,10 @@ struct proxy {
struct list inspect_rules; /* inspection rules */
struct list l4_rules; /* layer4 rules */
} tcp_req;
struct { /* TCP request processing */
unsigned int inspect_delay; /* inspection delay */
struct list inspect_rules; /* inspection rules */
} tcp_rep;
int acl_requires; /* Elements required to satisfy all ACLs (ACL_USE_*) */
struct server *srv, defsrv; /* known servers; default server configuration */
int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */

View File

@ -1034,12 +1034,14 @@ static void init_new_proxy(struct proxy *p)
LIST_INIT(&p->sticking_rules);
LIST_INIT(&p->storersp_rules);
LIST_INIT(&p->tcp_req.inspect_rules);
LIST_INIT(&p->tcp_rep.inspect_rules);
LIST_INIT(&p->tcp_req.l4_rules);
LIST_INIT(&p->req_add);
LIST_INIT(&p->rsp_add);
/* Timeouts are defined as -1 */
proxy_reset_timeouts(p);
p->tcp_rep.inspect_delay = TICK_ETERNITY;
}
void init_default_instance()
@ -2768,8 +2770,10 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
err_code |= warnif_cond_requires_resp(cond, file, linenum);
if (flags & STK_ON_RSP)
err_code |= warnif_cond_requires_req(cond, file, linenum);
else
err_code |= warnif_cond_requires_resp(cond, file, linenum);
rule = (struct sticking_rule *)calloc(1, sizeof(*rule));
rule->cond = cond;
@ -5970,6 +5974,9 @@ out_uri_auth_compat:
!LIST_ISEMPTY(&curproxy->tcp_req.inspect_rules))
curproxy->be_req_ana |= AN_REQ_INSPECT_BE;
if (!LIST_ISEMPTY(&curproxy->tcp_rep.inspect_rules))
curproxy->be_rsp_ana |= AN_RES_INSPECT;
if (curproxy->mode == PR_MODE_HTTP) {
curproxy->be_req_ana |= AN_REQ_WAIT_HTTP | AN_REQ_HTTP_INNER | AN_REQ_HTTP_PROCESS_BE;
curproxy->be_rsp_ana |= AN_RES_WAIT_HTTP | AN_RES_HTTP_PROCESS_BE;

View File

@ -50,6 +50,7 @@
#include <proto/stick_table.h>
#include <proto/stream_sock.h>
#include <proto/task.h>
#include <proto/buffers.h>
#ifdef CONFIG_HAP_CTTPROXY
#include <import/ip_tproxy.h>
@ -750,6 +751,91 @@ int tcp_inspect_request(struct session *s, struct buffer *req, int an_bit)
return 1;
}
/* 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 session *s, struct buffer *rep, int an_bit)
{
struct tcp_rule *rule;
int partial;
DPRINTF(stderr,"[%u] %s: session=%p b=%p, exp(r,w)=%u,%u bf=%08x bl=%d analysers=%02x\n",
now_ms, __FUNCTION__,
s,
rep,
rep->rex, rep->wex,
rep->flags,
rep->l,
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 & BF_SHUTR || tick_is_expired(rep->analyse_exp, now_ms))
partial = 0;
else
partial = ACL_PARTIAL;
list_for_each_entry(rule, &s->be->tcp_rep.inspect_rules, list) {
int ret = ACL_PAT_PASS;
if (rule->cond) {
ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, ACL_DIR_RTR | partial);
if (ret == ACL_PAT_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_ifset(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) {
/* we have a matching rule. */
if (rule->action == TCP_ACT_REJECT) {
buffer_abort(rep);
buffer_abort(s->req);
rep->analysers = 0;
s->be->counters.denied_resp++;
if (s->listener->counters)
s->listener->counters->denied_resp++;
if (!(s->flags & SN_ERR_MASK))
s->flags |= SN_ERR_PRXCOND;
if (!(s->flags & SN_FINST_MASK))
s->flags |= SN_FINST_D;
return 0;
}
else {
/* otherwise accept */
break;
}
}
}
/* 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
@ -822,6 +908,51 @@ int tcp_exec_req_rules(struct session *s)
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 tcp_rule *rule, char *err, int errlen)
{
if (curpx == defpx || !(curpx->cap & PR_CAP_BE)) {
snprintf(err, errlen, "%s %s is only allowed in 'backend' sections",
args[0], args[1]);
return -1;
}
if (strcmp(args[arg], "accept") == 0) {
arg++;
rule->action = TCP_ACT_ACCEPT;
}
else if (strcmp(args[arg], "reject") == 0) {
arg++;
rule->action = TCP_ACT_REJECT;
}
else {
snprintf(err, errlen,
"'%s %s' expects 'accept' or 'reject' in %s '%s' (was '%s')",
args[0], args[1], 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(NULL, 0, curpx, (const char **)args+arg)) == NULL) {
snprintf(err, errlen,
"error detected in %s '%s' while parsing '%s' condition",
proxy_type_str(curpx), curpx->id, args[arg]);
return -1;
}
}
else if (*args[arg]) {
snprintf(err, errlen,
"'%s %s %s' only accepts 'if' or 'unless', in %s '%s' (was '%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,
@ -890,6 +1021,89 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type,
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, char *err, int errlen)
{
const char *ptr = NULL;
unsigned int val;
int retlen;
int warn = 0;
int arg;
struct tcp_rule *rule;
if (!*args[1]) {
snprintf(err, errlen, "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)) {
snprintf(err, errlen, "%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))) {
retlen = snprintf(err, errlen,
"'%s %s' expects a positive delay in milliseconds, in %s '%s'",
args[0], args[1], proxy_type_str(curpx), curpx->id);
if (ptr && retlen < errlen)
retlen += snprintf(err + retlen, errlen - retlen,
" (unexpected character '%c')", *ptr);
return -1;
}
if (curpx->tcp_rep.inspect_delay) {
snprintf(err, errlen, "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 = (struct tcp_rule *)calloc(1, sizeof(*rule));
LIST_INIT(&rule->list);
arg = 1;
if (strcmp(args[1], "content") == 0) {
arg++;
if (tcp_parse_response_rule(args, arg, section_type, curpx, defpx, rule, err, errlen) < 0)
goto error;
if (rule->cond && (rule->cond->requires & ACL_USE_L6REQ_VOLATILE)) {
struct acl *acl;
const char *name;
acl = cond_find_require(rule->cond, ACL_USE_L6REQ_VOLATILE);
name = acl ? acl->name : "(unknown)";
retlen = snprintf(err, errlen,
"acl '%s' involves some request-only criteria which will be ignored.",
name);
warn++;
}
LIST_ADDQ(&curpx->tcp_rep.inspect_rules, &rule->list);
}
else {
retlen = snprintf(err, errlen,
"'%s' expects 'inspect-delay' or 'content' in %s '%s' (was '%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.
*/
@ -1129,6 +1343,7 @@ pattern_fetch_dport(struct proxy *px, struct session *l4, void *l7, int dir,
static struct cfg_kw_list cfg_kws = {{ },{
{ CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
{ CFG_LISTEN, "tcp-response", tcp_parse_tcp_rep },
{ 0, NULL, NULL },
}};

View File

@ -1585,6 +1585,12 @@ struct task *process_session(struct task *t)
while (ana_list && max_loops--) {
/* Warning! ensure that analysers are always placed in ascending order! */
if (ana_list & AN_RES_INSPECT) {
if (!tcp_inspect_response(s, s->rep, AN_RES_INSPECT))
break;
UPDATE_ANALYSERS(s->rep->analysers, ana_list, ana_back, AN_RES_INSPECT);
}
if (ana_list & AN_RES_WAIT_HTTP) {
if (!http_wait_for_response(s, s->rep, AN_RES_WAIT_HTTP))
break;