From 18bf01e90034c4b8f0ab338d2e6e1e8b64a1df47 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 13 Jun 2014 16:18:52 +0200 Subject: [PATCH] MEDIUM: tcp: add a new tcp-request capture directive This new directive captures the specified fetch expression, converts it to text and puts it into the next capture slot. The capture slots are shared with header captures so that it is possible to dump all captures at once or selectively in logs and header processing. The purpose is to permit logs to contain whatever payload is found in a request, for example bytes at a fixed location or the SNI of forwarded SSL traffic. --- doc/configuration.txt | 25 ++++++-- src/proto_tcp.c | 130 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 142 insertions(+), 13 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 89bdf986a5..43d4760919 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -7171,7 +7171,7 @@ tcp-request connection [{if | unless} ] accept the incoming connection. There is no specific limit to the number of rules which may be inserted. - Three types of actions are supported : + Five types of actions are supported : - accept : accepts the connection if the condition is true (when used with "if") or false (when used with "unless"). The first such rule executed ends @@ -7200,6 +7200,18 @@ tcp-request connection [{if | unless} ] of load balancers are passed through by traffic coming from public hosts. + - capture len : + This only applies to "tcp-request content" rules. It captures sample + expression from the request buffer, and converts it to a + string of at most characters. The resulting string is stored into + the next request "capture" slot, so it will possibly appear next to + some captured HTTP headers. It will then automatically appear in the + logs, and it will be possible to extract it using sample fetch rules to + feed it into headers or anything. The length should be limited given + that this size will be allocated for each capture during the whole + session life. Since it applies to Please check section 7.3 (Fetching + samples) and "capture request header" for more information. + - { track-sc0 | track-sc1 | track-sc2 } [table ] : enables tracking of sticky counters from current connection. These rules do not stop evaluation and do not change default action. Two sets @@ -7278,8 +7290,8 @@ tcp-request content [{if | unless} ] Arguments : defines the action to perform if the condition applies. Valid actions include : "accept", "reject", "track-sc0", "track-sc1", - and "track-sc2". See "tcp-request connection" above for their - signification. + "track-sc2" and "capture". See "tcp-request connection" above + for their signification. is a standard layer 4-7 ACL-based condition (see section 7). @@ -7307,9 +7319,10 @@ tcp-request content [{if | unless} ] contents. There is no specific limit to the number of rules which may be inserted. - Three types of actions are supported : - - accept : - - reject : + Four types of actions are supported : + - accept : the request is accepted + - reject : the request is rejected and the connection is closed + - capture : the specified sample expression is captured - { track-sc0 | track-sc1 | track-sc2 } [table
] They have the same meaning as their counter-parts in "tcp-request connection" diff --git a/src/proto_tcp.c b/src/proto_tcp.c index a672de4157..65c4fdad37 100644 --- a/src/proto_tcp.c +++ b/src/proto_tcp.c @@ -35,6 +35,7 @@ #include #include +#include #include #include @@ -990,13 +991,8 @@ int tcp_inspect_request(struct session *s, struct channel *req, int an_bit) if (rule->cond) { ret = acl_exec_cond(rule->cond, s->be, s, &s->txn, SMP_OPT_DIR_REQ | partial); - if (ret == ACL_TEST_MISS) { - channel_dont_connect(req); - /* just set the request timeout once at the beginning of the request */ - if (!tick_isset(req->analyse_exp) && s->be->tcp_req.inspect_delay) - req->analyse_exp = tick_add(now_ms, s->be->tcp_req.inspect_delay); - return 0; - } + if (ret == ACL_TEST_MISS) + goto missing_data; ret = acl_pass(ret); if (rule->cond->pol == ACL_COND_UNLESS) @@ -1040,6 +1036,32 @@ int tcp_inspect_request(struct session *s, struct channel *req, int an_bit) stkctr_set_flags(&s->stkctr[tcp_trk_idx(rule->action)], STKCTR_TRACK_BACKEND); } } + else if (rule->action == TCP_ACT_CAPTURE) { + struct sample *key; + struct cap_hdr *h = rule->act_prm.cap.hdr; + char **cap = s->txn.req.cap; + int len; + + key = sample_fetch_string(s->be, s, &s->txn, SMP_OPT_DIR_REQ | partial, rule->act_prm.cap.expr); + if (!key) + continue; + + if (key->flags & SMP_F_MAY_CHANGE) + goto missing_data; + + if (cap[h->index] == NULL) + cap[h->index] = pool_alloc2(h->pool); + + if (cap[h->index] == NULL) /* no more capture memory */ + continue; + + len = key->data.str.len; + if (len > h->len) + len = h->len; + + memcpy(cap[h->index], key->data.str.str, len); + cap[h->index][len] = 0; + } else { /* otherwise accept */ break; @@ -1053,6 +1075,14 @@ int tcp_inspect_request(struct session *s, struct channel *req, int an_bit) req->analysers &= ~an_bit; req->analyse_exp = TICK_ETERNITY; return 1; + + missing_data: + channel_dont_connect(req); + /* just set the request timeout once at the beginning of the request */ + if (!tick_isset(req->analyse_exp) && s->be->tcp_req.inspect_delay) + req->analyse_exp = tick_add(now_ms, s->be->tcp_req.inspect_delay); + return 0; + } /* This function performs the TCP response analysis on the current response. It @@ -1287,6 +1317,92 @@ static int tcp_parse_request_rule(char **args, int arg, int section_type, arg++; rule->action = TCP_ACT_REJECT; } + else if (strcmp(args[arg], "capture") == 0) { + struct sample_expr *expr; + struct cap_hdr *hdr; + int kw = arg; + int len = 0; + + if (!(curpx->cap & PR_CAP_FE)) { + memprintf(err, + "'%s %s %s' : proxy '%s' has no frontend capability", + args[0], args[1], args[kw], curpx->id); + return -1; + } + + if (!(where & SMP_VAL_FE_REQ_CNT)) { + memprintf(err, + "'%s %s' is not allowed in '%s %s' rules in %s '%s'", + args[arg], args[arg+1], args[0], args[1], proxy_type_str(curpx), curpx->id); + return -1; + } + + arg++; + + curpx->conf.args.ctx = ARGC_CAP; + expr = sample_parse_expr(args, &arg, file, line, err, &curpx->conf.args); + if (!expr) { + memprintf(err, + "'%s %s %s' : %s", + args[0], args[1], args[kw], *err); + return -1; + } + + if (!(expr->fetch->val & where)) { + memprintf(err, + "'%s %s %s' : fetch method '%s' extracts information from '%s', none of which is available here", + args[0], args[1], args[kw], args[arg-1], sample_src_names(expr->fetch->use)); + free(expr); + return -1; + } + + if (strcmp(args[arg], "len") == 0) { + arg++; + if (!args[arg]) { + memprintf(err, + "'%s %s %s' : missing length value", + args[0], args[1], args[kw]); + free(expr); + return -1; + } + /* we copy the table name for now, it will be resolved later */ + len = atoi(args[arg]); + if (len <= 0) { + memprintf(err, + "'%s %s %s' : length must be > 0", + args[0], args[1], args[kw]); + free(expr); + return -1; + } + arg++; + } + + if (!len) { + memprintf(err, + "'%s %s %s' : a positive 'len' argument is mandatory", + args[0], args[1], args[kw]); + free(expr); + return -1; + } + + hdr = calloc(sizeof(struct cap_hdr), 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 hdr_idx struct for HTTP parsing */ + curpx->http_needed |= !!(expr->fetch->use & SMP_USE_HTTP_ANY); + + rule->act_prm.cap.expr = expr; + rule->act_prm.cap.hdr = hdr; + rule->action = TCP_ACT_CAPTURE; + } else if (strncmp(args[arg], "track-sc", 8) == 0 && args[arg][9] == '\0' && args[arg][8] >= '0' && args[arg][8] <= '0' + MAX_SESS_STKCTR) { /* track-sc 0..9 */ -- 2.39.5