[MAJOR] implement tcp request content inspection
Some people need to inspect contents of TCP requests before deciding to forward a connection or not. A future extension of this demand might consist in selecting a server farm depending on the protocol detected in the request. For this reason, a new state CL_STINSPECT has been added on the client side. It is immediately entered upon accept() if the statement "tcp-request inspect-delay <xxx>" is found in the frontend configuration. Haproxy will then wait up to this amount of time trying to find a matching ACL, and will either accept or reject the connection depending on the "tcp-request content <action> {if|unless}" rules, where <action> is either "accept" or "reject". Note that it only waits that long if no definitive verdict can be found earlier. That generally implies calling a fetch() function which does not have enough information to decode some contents, or a match() function which only finds the beginning of what it's looking for. It is only at the ACL level that partial data may be processed as such, because we need to distinguish between MISS and FAIL *before* applying the term negation. Thus it is enough to add "| ACL_PARTIAL" to the last argument when calling acl_exec_cond() to indicate that we expect ACL_PAT_MISS to be returned if some data is missing (for fetch() or match()). This is the only case we may return this value. For this reason, the ACL check in process_cli() has become a lot simpler. A new ACL "req_len" of type "int" has been added. Right now it is already possible to drop requests which talk too early (eg: for SMTP) or which don't talk at all (eg: HTTP/SSL). Also, the acl fetch() functions have been extended in order to permit reporting of missing data in case of fetch failure, using the ACL_TEST_F_MAY_CHANGE flag. The default behaviour is unchanged, and if no rule matches, the request is accepted. As a side effect, all layer 7 fetching functions have been cleaned up so that they now check for the validity of the layer 7 pointer before dereferencing it.
This commit is contained in:
parent
9de1bbd004
commit
b686644ad8
@ -23,6 +23,7 @@
|
||||
#define _PROTO_PROTO_TCP_H
|
||||
|
||||
#include <common/config.h>
|
||||
#include <types/proto_tcp.h>
|
||||
#include <types/session.h>
|
||||
#include <types/task.h>
|
||||
|
||||
|
@ -78,13 +78,16 @@ enum {
|
||||
ACL_TEST_F_VOL_TXN = 1 << 5, /* result sensitive to new transaction (eg: persist) */
|
||||
ACL_TEST_F_VOL_SESS = 1 << 6, /* result sensitive to new session (eg: IP) */
|
||||
ACL_TEST_F_VOLATILE = (1<<2)|(1<<3)|(1<<4)|(1<<5)|(1<<6),
|
||||
ACL_TEST_F_FETCH_MORE = 1 << 7, /* if test does not match, retry with next entry */
|
||||
ACL_TEST_F_FETCH_MORE = 1 << 7, /* if test does not match, retry with next entry (for multi-match) */
|
||||
ACL_TEST_F_MAY_CHANGE = 1 << 8, /* if test does not match, retry later (eg: request size) */
|
||||
};
|
||||
|
||||
/* ACLs can be evaluated on requests and on responses. */
|
||||
/* ACLs can be evaluated on requests and on responses, and on partial or complete data */
|
||||
enum {
|
||||
ACL_DIR_REQ = 0, /* ACL evaluated on request */
|
||||
ACL_DIR_RTR, /* ACL evaluated on response */
|
||||
ACL_DIR_RTR = (1 << 0), /* ACL evaluated on response */
|
||||
ACL_DIR_MASK = (ACL_DIR_REQ | ACL_DIR_RTR),
|
||||
ACL_PARTIAL = (1 << 1), /* partial data, return MISS if data are missing */
|
||||
};
|
||||
|
||||
/* possible flags for expressions or patterns */
|
||||
|
@ -24,19 +24,6 @@
|
||||
|
||||
#include <common/config.h>
|
||||
|
||||
/*
|
||||
* FIXME: break this into HTTP state and TCP socket state.
|
||||
* See server.h for the other end.
|
||||
*/
|
||||
|
||||
/* different possible states for the client side */
|
||||
#define CL_STHEADERS 0
|
||||
#define CL_STDATA 1
|
||||
#define CL_STSHUTR 2
|
||||
#define CL_STSHUTW 3
|
||||
#define CL_STCLOSE 4
|
||||
|
||||
|
||||
#endif /* _TYPES_CLIENT_H */
|
||||
|
||||
/*
|
||||
|
@ -29,9 +29,16 @@
|
||||
|
||||
/*
|
||||
* FIXME: break this into HTTP state and TCP socket state.
|
||||
* See client.h for the other end.
|
||||
*/
|
||||
|
||||
/* different possible states for the client side */
|
||||
#define CL_STINSPECT 0
|
||||
#define CL_STHEADERS 1
|
||||
#define CL_STDATA 2
|
||||
#define CL_STSHUTR 3
|
||||
#define CL_STSHUTW 4
|
||||
#define CL_STCLOSE 5
|
||||
|
||||
/* different possible states for the server side */
|
||||
#define SV_STIDLE 0
|
||||
#define SV_STANALYZE 1 /* this server state is set by the client to study the body for server assignment */
|
||||
|
49
include/types/proto_tcp.h
Normal file
49
include/types/proto_tcp.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
include/types/proto_tcp.h
|
||||
This file contains TCP protocol definitions.
|
||||
|
||||
Copyright (C) 2000-2008 Willy Tarreau - w@1wt.eu
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation, version 2.1
|
||||
exclusively.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#ifndef _TYPES_PROTO_TCP_H
|
||||
#define _TYPES_PROTO_TCP_H
|
||||
|
||||
#include <common/config.h>
|
||||
#include <common/mini-clist.h>
|
||||
|
||||
#include <types/acl.h>
|
||||
|
||||
/* Layer4 accept/reject rules */
|
||||
enum {
|
||||
TCP_ACT_ACCEPT = 1,
|
||||
TCP_ACT_REJECT = 2,
|
||||
};
|
||||
|
||||
struct tcp_rule {
|
||||
struct list list;
|
||||
struct acl_cond *cond;
|
||||
int action;
|
||||
};
|
||||
|
||||
#endif /* _TYPES_PROTO_TCP_H */
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-indent-level: 8
|
||||
* c-basic-offset: 8
|
||||
* End:
|
||||
*/
|
@ -131,6 +131,10 @@ struct proxy {
|
||||
struct list block_cond; /* early blocking conditions (chained) */
|
||||
struct list redirect_rules; /* content redirecting rules (chained) */
|
||||
struct list switching_rules; /* content switching rules (chained) */
|
||||
struct { /* TCP request processing */
|
||||
int inspect_delay; /* inspection delay */
|
||||
struct list inspect_rules; /* inspection rules */
|
||||
} tcp_req;
|
||||
struct server *srv; /* known servers */
|
||||
int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */
|
||||
|
||||
|
@ -115,6 +115,7 @@ struct session {
|
||||
struct server *prev_srv; /* the server the was running on, after a redispatch, otherwise NULL */
|
||||
struct pendconn *pend_pos; /* if not NULL, points to the position in the pending queue */
|
||||
struct http_txn txn; /* current HTTP transaction being processed. Should become a list. */
|
||||
int inspect_exp; /* expiration date for data to be inspected, in ticks */
|
||||
struct {
|
||||
int logwait; /* log fields waiting to be collected : LW_* */
|
||||
struct timeval accept_date; /* date of the accept() in user date */
|
||||
|
24
src/acl.c
24
src/acl.c
@ -818,11 +818,16 @@ struct acl_cond *parse_acl_cond(const char **args, struct list *known_acl, int p
|
||||
}
|
||||
|
||||
/* Execute condition <cond> and return either ACL_PAT_FAIL, ACL_PAT_MISS or
|
||||
* ACL_PAT_PASS depending on the test results. This function only computes the
|
||||
* condition, it does not apply the polarity required by IF/UNLESS, it's up to
|
||||
* the caller to do this using something like this :
|
||||
* ACL_PAT_PASS depending on the test results. ACL_PAT_MISS may only be
|
||||
* returned if <dir> contains ACL_PARTIAL, indicating that incomplete data
|
||||
* is being examined.
|
||||
* This function only computes the condition, it does not apply the polarity
|
||||
* required by IF/UNLESS, it's up to the caller to do this using something like
|
||||
* this :
|
||||
*
|
||||
* res = acl_pass(res);
|
||||
* if (res == ACL_PAT_MISS)
|
||||
* return 0;
|
||||
* if (cond->pol == ACL_COND_UNLESS)
|
||||
* res = !res;
|
||||
*/
|
||||
@ -866,8 +871,12 @@ int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, v
|
||||
/* we need to reset context and flags */
|
||||
memset(&test, 0, sizeof(test));
|
||||
fetch_next:
|
||||
if (!expr->kw->fetch(px, l4, l7, dir, expr, &test))
|
||||
if (!expr->kw->fetch(px, l4, l7, dir, expr, &test)) {
|
||||
/* maybe we could not fetch because of missing data */
|
||||
if (test.flags & ACL_TEST_F_MAY_CHANGE && dir & ACL_PARTIAL)
|
||||
acl_res |= ACL_PAT_MISS;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* apply all tests to this value */
|
||||
list_for_each_entry(pattern, &expr->patterns, list) {
|
||||
@ -898,6 +907,13 @@ int acl_exec_cond(struct acl_cond *cond, struct proxy *px, struct session *l4, v
|
||||
|
||||
if (test.flags & ACL_TEST_F_FETCH_MORE)
|
||||
goto fetch_next;
|
||||
|
||||
/* sometimes we know the fetched data is subject to change
|
||||
* later and give another chance for a new match (eg: request
|
||||
* size, time, ...)
|
||||
*/
|
||||
if (test.flags & ACL_TEST_F_MAY_CHANGE && dir & ACL_PARTIAL)
|
||||
acl_res |= ACL_PAT_MISS;
|
||||
}
|
||||
/*
|
||||
* Here we have the result of an ACL (cached or not).
|
||||
|
@ -614,6 +614,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv)
|
||||
LIST_INIT(&curproxy->redirect_rules);
|
||||
LIST_INIT(&curproxy->mon_fail_cond);
|
||||
LIST_INIT(&curproxy->switching_rules);
|
||||
LIST_INIT(&curproxy->tcp_req.inspect_rules);
|
||||
|
||||
/* Timeouts are defined as -1, so we cannot use the zeroed area
|
||||
* as a default value.
|
||||
|
17
src/client.c
17
src/client.c
@ -168,7 +168,10 @@ int event_accept(int fd) {
|
||||
* backend must be assigned if set.
|
||||
*/
|
||||
if (p->mode == PR_MODE_HTTP) {
|
||||
s->cli_state = CL_STHEADERS;
|
||||
if (s->fe->tcp_req.inspect_delay)
|
||||
s->cli_state = CL_STINSPECT;
|
||||
else
|
||||
s->cli_state = CL_STHEADERS;
|
||||
} else {
|
||||
/* We must assign any default backend now since
|
||||
* there will be no header processing.
|
||||
@ -178,7 +181,10 @@ int event_accept(int fd) {
|
||||
s->be = p->defbe.be;
|
||||
s->flags |= SN_BE_ASSIGNED;
|
||||
}
|
||||
s->cli_state = CL_STDATA; /* no HTTP headers for non-HTTP proxies */
|
||||
if (s->fe->tcp_req.inspect_delay)
|
||||
s->cli_state = CL_STINSPECT;
|
||||
else
|
||||
s->cli_state = CL_STDATA; /* no HTTP headers for non-HTTP proxies */
|
||||
}
|
||||
|
||||
s->srv_state = SV_STIDLE;
|
||||
@ -340,7 +346,7 @@ int event_accept(int fd) {
|
||||
|
||||
buffer_init(s->req);
|
||||
s->req->rlim += BUFSIZE;
|
||||
if (s->cli_state == CL_STHEADERS) /* reserve some space for header rewriting */
|
||||
if (p->mode == PR_MODE_HTTP) /* reserve some space for header rewriting */
|
||||
s->req->rlim -= MAXREWRITE;
|
||||
|
||||
s->req->rto = s->fe->timeout.client;
|
||||
@ -390,6 +396,7 @@ int event_accept(int fd) {
|
||||
s->rep->rex = TICK_ETERNITY;
|
||||
s->rep->wex = TICK_ETERNITY;
|
||||
s->txn.exp = TICK_ETERNITY;
|
||||
s->inspect_exp = TICK_ETERNITY;
|
||||
t->expire = TICK_ETERNITY;
|
||||
|
||||
if (s->fe->timeout.client) {
|
||||
@ -407,6 +414,10 @@ int event_accept(int fd) {
|
||||
s->txn.exp = tick_add(now_ms, s->fe->timeout.httpreq);
|
||||
t->expire = tick_first(t->expire, s->txn.exp);
|
||||
}
|
||||
else if (s->cli_state == CL_STINSPECT && s->fe->tcp_req.inspect_delay) {
|
||||
s->inspect_exp = tick_add(now_ms, s->fe->tcp_req.inspect_delay);
|
||||
t->expire = tick_first(t->expire, s->inspect_exp);
|
||||
}
|
||||
|
||||
if (p->mode != PR_MODE_HEALTH)
|
||||
task_wakeup(t);
|
||||
|
162
src/proto_http.c
162
src/proto_http.c
@ -51,6 +51,7 @@
|
||||
#include <proto/fd.h>
|
||||
#include <proto/log.h>
|
||||
#include <proto/hdr_idx.h>
|
||||
#include <proto/proto_tcp.h>
|
||||
#include <proto/proto_http.h>
|
||||
#include <proto/queue.h>
|
||||
#include <proto/senddata.h>
|
||||
@ -680,6 +681,8 @@ void process_session(struct task *t, int *next)
|
||||
t->expire = tick_first(t->expire, s->req->cex);
|
||||
if (s->cli_state == CL_STHEADERS)
|
||||
t->expire = tick_first(t->expire, s->txn.exp);
|
||||
else if (s->cli_state == CL_STINSPECT)
|
||||
t->expire = tick_first(t->expire, s->inspect_exp);
|
||||
|
||||
/* restore t to its place in the task list */
|
||||
task_queue(t);
|
||||
@ -1550,7 +1553,104 @@ int process_cli(struct session *t)
|
||||
req->rex.tv_sec, req->rex.tv_usec,
|
||||
rep->wex.tv_sec, rep->wex.tv_usec);
|
||||
|
||||
if (c == CL_STHEADERS) {
|
||||
if (c == CL_STINSPECT) {
|
||||
struct tcp_rule *rule;
|
||||
int partial;
|
||||
|
||||
/* We will abort if we encounter a read error. In theory,
|
||||
* we should not abort if we get a close, it might be
|
||||
* valid, also very unlikely. FIXME: we'll abort for now,
|
||||
* this will be easier to change later.
|
||||
*/
|
||||
if (unlikely(req->flags & (BF_READ_ERROR | BF_READ_NULL))) {
|
||||
t->inspect_exp = TICK_ETERNITY;
|
||||
buffer_shutr(req);
|
||||
fd_delete(t->cli_fd);
|
||||
t->cli_state = CL_STCLOSE;
|
||||
t->fe->failed_req++;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_CLICL;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Abort if client read timeout has expired */
|
||||
else if (unlikely(tick_is_expired(req->rex, now_ms))) {
|
||||
t->inspect_exp = TICK_ETERNITY;
|
||||
buffer_shutr(req);
|
||||
fd_delete(t->cli_fd);
|
||||
t->cli_state = CL_STCLOSE;
|
||||
t->fe->failed_req++;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_CLITO;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 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 (tick_is_expired(t->inspect_exp, now_ms))
|
||||
partial = 0;
|
||||
else
|
||||
partial = ACL_PARTIAL;
|
||||
|
||||
list_for_each_entry(rule, &t->fe->tcp_req.inspect_rules, list) {
|
||||
int ret = ACL_PAT_PASS;
|
||||
|
||||
if (rule->cond) {
|
||||
ret = acl_exec_cond(rule->cond, t->fe, t, NULL, ACL_DIR_REQ | partial);
|
||||
if (ret == ACL_PAT_MISS) {
|
||||
req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
|
||||
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_shutr(req);
|
||||
fd_delete(t->cli_fd);
|
||||
t->cli_state = CL_STCLOSE;
|
||||
t->fe->failed_req++;
|
||||
if (!(t->flags & SN_ERR_MASK))
|
||||
t->flags |= SN_ERR_PRXCOND;
|
||||
if (!(t->flags & SN_FINST_MASK))
|
||||
t->flags |= SN_FINST_R;
|
||||
t->inspect_exp = TICK_ETERNITY;
|
||||
return 1;
|
||||
}
|
||||
/* otherwise accept */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if we get there, it means we have no rule which matches, so
|
||||
* we apply the default accept.
|
||||
*/
|
||||
req->rex = tick_add_ifset(now_ms, t->fe->timeout.client);
|
||||
if (t->fe->mode == PR_MODE_HTTP) {
|
||||
t->cli_state = CL_STHEADERS;
|
||||
t->txn.exp = tick_add_ifset(now_ms, t->fe->timeout.httpreq);
|
||||
} else {
|
||||
t->cli_state = CL_STDATA;
|
||||
}
|
||||
t->inspect_exp = TICK_ETERNITY;
|
||||
return 1;
|
||||
}
|
||||
else if (c == CL_STHEADERS) {
|
||||
/*
|
||||
* Now parse the partial (or complete) lines.
|
||||
* We will check the request syntax, and also join multi-line
|
||||
@ -2606,7 +2706,7 @@ int process_srv(struct session *t)
|
||||
* we need to defer server selection until more data arrives, if possible.
|
||||
* This is rare, and only if balancing on parameter hash with values in the entity of a POST
|
||||
*/
|
||||
if (c == CL_STHEADERS )
|
||||
if (c == CL_STHEADERS || c == CL_STINSPECT)
|
||||
return 0; /* stay in idle, waiting for data to reach the client side */
|
||||
else if (c == CL_STCLOSE || c == CL_STSHUTW ||
|
||||
(c == CL_STSHUTR &&
|
||||
@ -5202,6 +5302,9 @@ acl_fetch_meth(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
int meth;
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5259,6 +5362,9 @@ acl_fetch_rqver(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
char *ptr;
|
||||
int len;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5284,6 +5390,9 @@ acl_fetch_stver(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
char *ptr;
|
||||
int len;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5310,6 +5419,9 @@ acl_fetch_stcode(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
char *ptr;
|
||||
int len;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5328,8 +5440,12 @@ acl_fetch_url(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
@ -5348,8 +5464,12 @@ acl_fetch_url_ip(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
@ -5376,8 +5496,12 @@ acl_fetch_url_port(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
@ -5404,6 +5528,9 @@ acl_fetch_hdr(struct proxy *px, struct session *l4, void *l7, char *sol,
|
||||
struct hdr_idx *idx = &txn->hdr_idx;
|
||||
struct hdr_ctx *ctx = (struct hdr_ctx *)test->ctx.a;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (!(test->flags & ACL_TEST_F_FETCH_MORE))
|
||||
/* search for header from the beginning */
|
||||
ctx->idx = 0;
|
||||
@ -5427,8 +5554,12 @@ acl_fetch_chdr(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
@ -5442,6 +5573,9 @@ acl_fetch_shdr(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5460,6 +5594,9 @@ acl_fetch_hdr_cnt(struct proxy *px, struct session *l4, void *l7, char *sol,
|
||||
struct hdr_ctx ctx;
|
||||
int cnt;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
ctx.idx = 0;
|
||||
cnt = 0;
|
||||
while (http_find_header2(expr->arg.str, expr->arg_len, sol, idx, &ctx))
|
||||
@ -5476,8 +5613,12 @@ acl_fetch_chdr_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
@ -5491,6 +5632,9 @@ acl_fetch_shdr_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5509,6 +5653,9 @@ acl_fetch_hdr_val(struct proxy *px, struct session *l4, void *l7, char *sol,
|
||||
struct hdr_idx *idx = &txn->hdr_idx;
|
||||
struct hdr_ctx *ctx = (struct hdr_ctx *)test->ctx.a;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (!(test->flags & ACL_TEST_F_FETCH_MORE))
|
||||
/* search for header from the beginning */
|
||||
ctx->idx = 0;
|
||||
@ -5531,8 +5678,12 @@ acl_fetch_chdr_val(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
@ -5546,6 +5697,9 @@ acl_fetch_shdr_val(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
{
|
||||
struct http_txn *txn = l7;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
@ -5562,8 +5716,12 @@ acl_fetch_path(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
struct http_txn *txn = l7;
|
||||
char *ptr, *end;
|
||||
|
||||
if (!txn)
|
||||
return 0;
|
||||
|
||||
if (txn->req.msg_state != HTTP_MSG_BODY)
|
||||
return 0;
|
||||
|
||||
if (txn->rsp.msg_state != HTTP_MSG_RPBEFORE)
|
||||
/* ensure the indexes are not affected */
|
||||
return 0;
|
||||
|
130
src/proto_tcp.c
130
src/proto_tcp.c
@ -24,6 +24,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <common/cfgparse.h>
|
||||
#include <common/compat.h>
|
||||
#include <common/config.h>
|
||||
#include <common/debug.h>
|
||||
@ -47,6 +48,7 @@
|
||||
#include <proto/fd.h>
|
||||
#include <proto/protocols.h>
|
||||
#include <proto/proto_tcp.h>
|
||||
#include <proto/proxy.h>
|
||||
#include <proto/queue.h>
|
||||
#include <proto/senddata.h>
|
||||
#include <proto/session.h>
|
||||
@ -329,11 +331,139 @@ void tcpv6_add_listener(struct listener *listener)
|
||||
proto_tcpv6.nb_listeners++;
|
||||
}
|
||||
|
||||
/* 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, char *err, int errlen)
|
||||
{
|
||||
const char *ptr = NULL;
|
||||
int val;
|
||||
int retlen;
|
||||
|
||||
if (!*args[1]) {
|
||||
snprintf(err, errlen, "missing argument for '%s' in %s '%s'",
|
||||
args[0], proxy_type_str(proxy), curpx->id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!strcmp(args[1], "inspect-delay")) {
|
||||
if (curpx == defpx) {
|
||||
snprintf(err, errlen, "%s %s is not allowed in 'defaults' sections",
|
||||
args[0], args[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!(curpx->cap & PR_CAP_FE)) {
|
||||
snprintf(err, errlen, "%s %s will be ignored because %s '%s' has no %s capability",
|
||||
args[0], args[1], proxy_type_str(proxy), curpx->id,
|
||||
"frontend");
|
||||
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(proxy), curpx->id);
|
||||
if (ptr && retlen < errlen)
|
||||
retlen += snprintf(err+retlen, errlen - retlen,
|
||||
" (unexpected character '%c')", *ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (curpx->tcp_req.inspect_delay) {
|
||||
snprintf(err, errlen, "ignoring %s %s (was already defined) in %s '%s'",
|
||||
args[0], args[1], proxy_type_str(proxy), curpx->id);
|
||||
return 1;
|
||||
}
|
||||
curpx->tcp_req.inspect_delay = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!strcmp(args[1], "content")) {
|
||||
int action;
|
||||
int pol = ACL_COND_NONE;
|
||||
struct acl_cond *cond;
|
||||
struct tcp_rule *rule;
|
||||
|
||||
if (curpx == defpx) {
|
||||
snprintf(err, errlen, "%s %s is not allowed in 'defaults' sections",
|
||||
args[0], args[1]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!strcmp(args[2], "accept"))
|
||||
action = TCP_ACT_ACCEPT;
|
||||
else if (!strcmp(args[2], "reject"))
|
||||
action = TCP_ACT_REJECT;
|
||||
else {
|
||||
retlen = 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[2]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pol = ACL_COND_NONE;
|
||||
cond = NULL;
|
||||
|
||||
if (!strcmp(args[3], "if"))
|
||||
pol = ACL_COND_IF;
|
||||
else if (!strcmp(args[3], "unless"))
|
||||
pol = ACL_COND_UNLESS;
|
||||
|
||||
/* Note: we consider "if TRUE" when there is no condition */
|
||||
if (pol != ACL_COND_NONE &&
|
||||
(cond = parse_acl_cond((const char **)args+4, &curpx->acl, pol)) == NULL) {
|
||||
retlen = snprintf(err, errlen,
|
||||
"Error detected in %s '%s' while parsing '%s' condition",
|
||||
proxy_type_str(curpx), curpx->id, args[3]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
rule = (struct tcp_rule *)calloc(1, sizeof(*rule));
|
||||
rule->cond = cond;
|
||||
rule->action = action;
|
||||
LIST_INIT(&rule->list);
|
||||
LIST_ADDQ(&curpx->tcp_req.inspect_rules, &rule->list);
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(err, errlen, "unknown argument '%s' after '%s' in %s '%s'",
|
||||
args[1], args[0], proxy_type_str(proxy), curpx->id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* return the number of bytes in the request buffer */
|
||||
static int
|
||||
acl_fetch_req_len(struct proxy *px, struct session *l4, void *l7, int dir,
|
||||
struct acl_expr *expr, struct acl_test *test)
|
||||
{
|
||||
if (!l4 || !l4->req)
|
||||
return 0;
|
||||
|
||||
test->i = l4->req->l;
|
||||
test->flags = ACL_TEST_F_VOLATILE | ACL_TEST_F_MAY_CHANGE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static struct cfg_kw_list cfg_kws = {{ },{
|
||||
{ CFG_LISTEN, "tcp-request", tcp_parse_tcp_req },
|
||||
{ 0, NULL, NULL },
|
||||
}};
|
||||
|
||||
static struct acl_kw_list acl_kws = {{ },{
|
||||
{ "req_len", acl_parse_int, acl_fetch_req_len, acl_match_int },
|
||||
{ NULL, NULL, NULL, NULL },
|
||||
}};
|
||||
|
||||
__attribute__((constructor))
|
||||
static void __tcp_protocol_init(void)
|
||||
{
|
||||
protocol_register(&proto_tcpv4);
|
||||
protocol_register(&proto_tcpv6);
|
||||
cfg_register_keywords(&cfg_kws);
|
||||
acl_register_keywords(&acl_kws);
|
||||
}
|
||||
|
||||
|
||||
|
35
tests/test-inspect-smtp.cfg
Normal file
35
tests/test-inspect-smtp.cfg
Normal file
@ -0,0 +1,35 @@
|
||||
# This is a test configuration. It listens on port 8025, waits for an incoming
|
||||
# connection, and applies the following rules :
|
||||
# - if the address is in the white list, then accept it and forward the
|
||||
# connection to the server (local port 25)
|
||||
# - if the address is in the black list, then immediately drop it
|
||||
# - otherwise, wait up to 3 seconds. If the client talks during this time,
|
||||
# drop the connection.
|
||||
# - then accept the connection if it passes all the tests.
|
||||
#
|
||||
# Note that the rules are evaluated at every new chunk of data read, and at
|
||||
# delay expiration. Rules which apply to incomplete data don't match as long
|
||||
# as the timer has not expired.
|
||||
|
||||
listen block-fake-mailers
|
||||
log 127.0.0.1:514 local0
|
||||
option tcplog
|
||||
|
||||
mode tcp
|
||||
bind :8025
|
||||
timeout client 6s
|
||||
timeout server 6s
|
||||
timeout connect 6s
|
||||
|
||||
tcp-request inspect-delay 4s
|
||||
|
||||
acl white_list src 127.0.0.2
|
||||
acl black_list src 127.0.0.3
|
||||
acl talkative req_len gt 0
|
||||
|
||||
tcp-request content accept if white_list
|
||||
tcp-request content reject if black_list
|
||||
tcp-request content reject if talkative
|
||||
|
||||
balance roundrobin
|
||||
server mail 127.0.0.1:25
|
Loading…
x
Reference in New Issue
Block a user