#define _PROTO_PROTO_TCP_H
#include <common/config.h>
+#include <types/proto_tcp.h>
#include <types/session.h>
#include <types/task.h>
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 */
#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 */
/*
/*
* 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 */
--- /dev/null
+/*
+ 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:
+ */
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) */
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 */
}
/* 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;
*/
/* 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) {
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).
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.
* 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.
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;
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;
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) {
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);
#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>
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);
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
* 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 &&
int meth;
struct http_txn *txn = l7;
+ if (!txn)
+ return 0;
+
if (txn->req.msg_state != HTTP_MSG_BODY)
return 0;
char *ptr;
int len;
+ if (!txn)
+ return 0;
+
if (txn->req.msg_state != HTTP_MSG_BODY)
return 0;
char *ptr;
int len;
+ if (!txn)
+ return 0;
+
if (txn->rsp.msg_state != HTTP_MSG_BODY)
return 0;
char *ptr;
int len;
+ if (!txn)
+ return 0;
+
if (txn->rsp.msg_state != HTTP_MSG_BODY)
return 0;
{
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;
{
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;
{
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;
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;
{
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;
{
struct http_txn *txn = l7;
+ if (!txn)
+ return 0;
+
if (txn->rsp.msg_state != HTTP_MSG_BODY)
return 0;
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))
{
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;
{
struct http_txn *txn = l7;
+ if (!txn)
+ return 0;
+
if (txn->rsp.msg_state != HTTP_MSG_BODY)
return 0;
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;
{
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;
{
struct http_txn *txn = l7;
+ if (!txn)
+ return 0;
+
if (txn->rsp.msg_state != HTTP_MSG_BODY)
return 0;
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;
#include <sys/types.h>
#include <sys/un.h>
+#include <common/cfgparse.h>
#include <common/compat.h>
#include <common/config.h>
#include <common/debug.h>
#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>
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);
}
--- /dev/null
+# 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