From 126ed95d67a0ed7c9e67844dd19bac6543dd97e2 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Sat, 12 May 2018 01:44:34 +0700 Subject: [PATCH] Add HTTP tables processing (#731). --- tempesta_fw/cfg.c | 29 +- tempesta_fw/cfg.h | 55 ++- tempesta_fw/http.c | 43 +- tempesta_fw/http_match.c | 248 +++++++---- tempesta_fw/http_match.h | 126 +++--- tempesta_fw/sched.c | 4 +- tempesta_fw/sched/tfw_sched_http.c | 612 ++++++++++++++++----------- tempesta_fw/server.h | 4 +- tempesta_fw/sock.c | 12 +- tempesta_fw/t/unit/test_http_match.c | 228 ++++++---- tempesta_fw/t/unit/test_sched_http.c | 58 ++- tempesta_fw/vhost.c | 11 - tempesta_fw/vhost.h | 1 - 13 files changed, 864 insertions(+), 567 deletions(-) diff --git a/tempesta_fw/cfg.c b/tempesta_fw/cfg.c index e2729531a..956f364d7 100644 --- a/tempesta_fw/cfg.c +++ b/tempesta_fw/cfg.c @@ -227,7 +227,6 @@ entry_set_name(TfwCfgEntry *e) BUG_ON(!e); BUG_ON(e->name); - TFW_WARN_NL("TEST10!!! 'entry_set_name()' -> e->name = '%s', e->ftoken = '%s'\n", e->name, e->ftoken); if (!rule) { name = e->ftoken; len = strlen(e->ftoken); @@ -702,8 +701,6 @@ entry_set_cond(TfwCfgEntry *e, token_t cond_type, const char *src, int len) BUG_ON(!e->ftoken); BUG_ON(e->name); - TFW_WARN_NL("TEST7!!! 'parse_cfg_entry()' -> PS_RULE_COND, (must be 4 or 5) ps->prev_t = '%d'\n", cond_type); - TFW_DBG3("set entry rule name '%.*s', 1st operand '%.*s', 2nd operand" " '%.*s', and condition type '%d'\n", name_len, name, (int)strlen(e->ftoken), e->ftoken, len, src, cond_type); @@ -720,9 +717,7 @@ entry_set_cond(TfwCfgEntry *e, token_t cond_type, const char *src, int len) if (!(e->name = alloc_and_copy_literal(name, name_len))) return -ENOMEM; - rule->type = cond_type == TOKEN_DEQSIGN - ? TFW_CFG_COND_EQUAL - : TFW_CFG_COND_NONEQUAL; + rule->inv = cond_type == TOKEN_DEQSIGN ? false : true; return 0; } @@ -763,11 +758,21 @@ parse_cfg_entry(TfwCfgParserState *ps) /* Every _PFSM_MOVE() invokes _read_next_token(), so when we enter * any state, we get a new token automatically. - * So: - * name key = value; - * ^ - * current literal is here; we need to store it as the name. - *///!!! change comment + * Three different situations may occur here: + * 1. In case of plain directive parsing: + * name key = value; + * ^ + * 2. In case of rule parsing: + * key == (!=) value -> action [= val] + * ^ + * 3. In case of parsing of pure action rule: + * -> action [= val] + * ^ + * current token is here; so at first we need to differentiate third + * situation, and in first two ones - save first token in special location + * to decide later whether use it as name for plain directive or as + * condition key for rule; in last two cases predefined rule name is used. + */ FSM_STATE(PS_START_NEW_ENTRY) { entry_reset(&ps->e); ps->e.line_no = ps->line_no; @@ -830,7 +835,7 @@ parse_cfg_entry(TfwCfgParserState *ps) read_next_token(ps); PFSM_COND_JMP_EXIT_ERROR(ps->t != TOKEN_SEMICOLON); FSM_JMP(PS_SEMICOLON); - }//!!! comments in rule states + } /* * Now we have a situation where at current position we don't know diff --git a/tempesta_fw/cfg.h b/tempesta_fw/cfg.h index 2d2bc3509..8bbfb8042 100644 --- a/tempesta_fw/cfg.h +++ b/tempesta_fw/cfg.h @@ -48,20 +48,6 @@ #define TFW_CFG_ENTRY_VAL_MAX 16 #define TFW_CFG_ENTRY_ATTR_MAX 16 -typedef enum { - TFW_CFG_COND_EQUAL = 0, - TFW_CFG_COND_NONEQUAL -} tfw_cfg_cond_t; - -//!!! comment -typedef struct { - tfw_cfg_cond_t type; - const char *fst; - const char *snd; - const char *act; - const char *val; -} TfwCfgRule; - #define TFW_CFG_RULE_NAME "rule" /** @@ -100,8 +86,35 @@ typedef struct { * Both sections and directives are expressed by TfwCfgEntry{} structure. * The difference is in @have_children flag which is true for sections. * - * This is a temporary structure that lives only during the parsing and - * acts as an interface between the parser FSM and TfwCfgSpec{}->handler. + * TfwCfgEntry{} structure also supports an alternative way to specify + * directives - in form of rules; structure TfwCfgRule{} (and field @rule + * of TfwCfgEntry{} structure) is responsible for rules parsing. The + * following rule: + * uri != "static*" -> mark = 1; + * + * if parsed, will have following representaion in TfwCfgEntry{}: + * TfwCfgEntry { + * .name = "rule", + * ... + * .rule = { + * .fst = "uri", + * .snd = "static*", + * .act = "mark", + * .val = "1", + * .inv = true + * }, + * ... + * } + * + * In above example @inv field of TfwCfgRule{} structure is responsible for + * comparison sign interpretation in rule condition part: + * "==" => false / "!=" => true + * + * @ftoken is an auxiliary internal field of TfwCfgEntry{} structure which + * helps parser to differentiate plain directives from rules. + * + * TfwCfgEntry{} is a temporary structure that lives only during the parsing + * and acts as an interface between the parser FSM and TfwCfgSpec{}->handler. * The parser accumulates data in a TfwCfgEntry{} instance. When the * current entry is complete, the parser executes the handler and then * destroys the instance. @@ -109,7 +122,15 @@ typedef struct { * These two members help to show a proper parsing error to a user: * @line_no - Current line number in the configuration file. * @line - Pointer to the start of the current line. - *///!!! add comment about @rule and @ftoken; + */ +typedef struct { + const char *fst; + const char *snd; + const char *act; + const char *val; + bool inv; +} TfwCfgRule; + typedef struct { struct { bool have_children : 1; diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index bdb788eae..1ab97c645 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -1899,13 +1899,6 @@ tfw_http_msg_create_sibling(TfwHttpMsg *hm, struct sk_buff **skb, return NULL; } - /* - * New message created, so it should be in whitelist if - * previous message was (for client connections). - */ - if (TFW_CONN_TYPE(hm->conn) & Conn_Clnt) - shm->flags |= hm->flags & TFW_HTTP_F_WHITELIST; - /* * The sibling message is set up to start with a new SKB. * The new SKB is split off from the original SKB and has @@ -1917,6 +1910,17 @@ tfw_http_msg_create_sibling(TfwHttpMsg *hm, struct sk_buff **skb, tfw_http_conn_msg_free(shm); return NULL; } + + /* + * New message created, so it should be in whitelist if + * previous message was (for client connections). Also + * we have new skb here and 'mark' propagation is needed. + */ + if (TFW_CONN_TYPE(hm->conn) & Conn_Clnt) { + shm->flags |= hm->flags & TFW_HTTP_F_WHITELIST; + nskb->mark = (*skb)->mark; + } + ss_skb_queue_tail(&shm->msg.skb_head, nskb); *skb = nskb; @@ -2596,12 +2600,23 @@ tfw_http_req_add_seq_queue(TfwHttpReq *req) static int tfw_http_req_set_context(TfwHttpReq *req) { - if (!(req->vhost = tfw_vhost_match((TfwMsg *)req))) - return -EINVAL; + bool block = false; + + if ((req->vhost = tfw_sched_get_vhost((TfwMsg *)req, &block))) { + req->location = tfw_location_match(req->vhost, &req->uri_path); + return 0; + } + if (block) { + TFW_INC_STAT_BH(clnt.msgs_filtout); + tfw_client_block(req, 500, "request has been filtered out" + " via http table"); + } else { + TFW_INC_STAT_BH(clnt.msgs_otherr); + tfw_client_drop(req, 500, "cannot find vhost for request"); + } + return -EINVAL; - req->location = tfw_location_match(req->vhost, &req->uri_path); - return 0; } static inline bool @@ -2739,12 +2754,8 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data) } /* Assign the right Vhost for this request. */ - if (tfw_http_req_set_context(req)) { - TFW_INC_STAT_BH(clnt.msgs_otherr); - tfw_client_drop(req, 500, "cannot find" - "Vhost for request"); + if (tfw_http_req_set_context(req)) return TFW_BLOCK; - } r = tfw_gfsm_move(&conn->state, TFW_HTTP_FSM_REQ_MSG, &data_up); TFW_DBG3("TFW_HTTP_FSM_REQ_MSG return code %d\n", r); diff --git a/tempesta_fw/http_match.c b/tempesta_fw/http_match.c index fea2b27cf..aa04c105c 100644 --- a/tempesta_fw/http_match.c +++ b/tempesta_fw/http_match.c @@ -1,39 +1,36 @@ /** * Tempesta FW * - * HTTP matching logic. + * HTTP table logic. * - * The matching process is driven by a "table" of rules that may look like this: - * @field @op @arg - * { TFW_HTTP_MATCH_F_HOST, TFW_HTTP_MATCH_O_EQ, "example.com" }, - * { TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_PREFIX, "/foo/bar" }, - * { TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_PREFIX, "/" }, + * The matching process is driven by a "chain" of rules that look like this: + * @field = (!=) @arg -> @action [ = @action_val ] + * { TFW_HTTP_MATCH_F_HOST, "*example.com", TFW_HTTP_MATCH_ACT_CHAIN }, + * { TFW_HTTP_MATCH_F_URI, "/foo/bar*", TFW_HTTP_MATCH_ACT_VHOST }, + * { TFW_HTTP_MATCH_F_URI, "/", TFW_HTTP_MATCH_ACT_MARK }, * - * The table is represented by a linked list TfwHttpMatchList that contains - * of TfwHttpMatchRule that has the field described above: + * The table is represented by a list of linked chains, that contain rules + * of TfwHttpMatchRule type that has the fields described above: * - @field is a field of a parsed HTTP request: method/uri/host/header/etc. - * - @op determines a comparison operator: eq/prefix/substring/regex/etc. - * - @arg is the second argument of the binary @op, its type is determined - * dynamically depending on the @field (may be number/string/addr/etc). + * - @op determines a comparison operator and depends on wildcard existance + * in @arg : "arg" => eq / "arg*" => prefix / "*arg" => suffix. + * - @act is a rule action with appropriate type (examples specified above). + * - @arg is the second argument in rule, its type is determined dynamically + * depending on the @field (may be number/string/addr/etc). * - * So the tfw_http_match_req() threads a HTTP request sequentally across all - * rules in the table and stops on a first matching rule (the rule is returned). - * The rule may be wrapped by a container structure and thus custom data may - * be attached to rules. + * So the tfw_sched_http_table_scan() threads a HTTP request sequentally across + * all rules in all chains in the table and stops on a first matching rule (the + * rule is returned). * - * Internally, each pair of @field and @op is dispatched to a corresponding - * match_* function. + * Internally, each @field is dispatched to a corresponding match_* function. * For example: - * TFW_HTTP_MATCH_F_METHOD + TFW_HTTP_MATCH_O_EQ => match_method_eq - * TFW_HTTP_MATCH_F_METHOD + TFW_HTTP_MATCH_O_IN => match_method_in - * However, different pairs may be dispatched to the same function: - * TFW_HTTP_MATCH_F_URI + TFW_HTTP_MATCH_O_EQ => match_uri - * TFW_HTTP_MATCH_F_URI + TFW_HTTP_MATCH_O_PREFIX => match_uri + * TFW_HTTP_MATCH_F_METHOD => match_method + * TFW_HTTP_MATCH_F_URI => match_uri * etc... * Each such match_*() function takes TfwHttpReq and TfwHttpMatchRule and * returns true if the given request matches to the given rule. * Such approach allows to keep the code structured and eases adding new - * @field/@op combinations. + * @field types. * Currently that is implemented with a multi-dimensional array of pointers * (the match_fn_tbl). However the code is critical for performance, so perhaps * this may be optimized to a kind of jump table. @@ -63,6 +60,7 @@ #include #include "http_match.h" #include "http_msg.h" +#include "cfg.h" /** * Look up a header in the @req->h_tbl by given @id, @@ -105,9 +103,9 @@ map_op_to_str_eq_flags(tfw_http_match_op_t op) { static const tfw_str_eq_flags_t flags_tbl[] = { [ 0 ... _TFW_HTTP_MATCH_O_COUNT ] = -1, - [TFW_HTTP_MATCH_O_EQ] = TFW_STR_EQ_DEFAULT, - [TFW_HTTP_MATCH_O_PREFIX] = TFW_STR_EQ_PREFIX, - [TFW_HTTP_MATCH_O_SUFFIX] = TFW_STR_EQ_DEFAULT, + [TFW_HTTP_MATCH_O_EQ] = TFW_STR_EQ_DEFAULT, + [TFW_HTTP_MATCH_O_PREFIX] = TFW_STR_EQ_PREFIX, + [TFW_HTTP_MATCH_O_SUFFIX] = TFW_STR_EQ_DEFAULT, }; BUG_ON(flags_tbl[op] < 0); return flags_tbl[op]; @@ -327,12 +325,18 @@ static bool match_wildcard(const TfwHttpReq *req, const TfwHttpMatchRule *rule) { if ((rule->op == TFW_HTTP_MATCH_O_WILDCARD) - && (rule->arg.type == TFW_HTTP_MATCH_A_WILDCARD) - && (rule->arg.len == 1) && (rule->arg.str[0] == '*')) + && (rule->arg.type == TFW_HTTP_MATCH_A_WILDCARD)) return true; return false; } +static bool +match_mark(const TfwHttpReq *req, const TfwHttpMatchRule *rule) +{ + BUG_ON(rule->op != TFW_HTTP_MATCH_O_EQ); + return req->msg.skb_head->mark == rule->arg.num; +} + typedef bool (*match_fn)(const TfwHttpReq *, const TfwHttpMatchRule *); @@ -345,32 +349,50 @@ static const match_fn match_fn_tbl[_TFW_HTTP_MATCH_F_COUNT] = { [TFW_HTTP_MATCH_F_HOST] = match_host, [TFW_HTTP_MATCH_F_METHOD] = match_method, [TFW_HTTP_MATCH_F_URI] = match_uri, + [TFW_HTTP_MATCH_F_MARK] = match_mark, }; /** - * Dispatch rule to a corresponding match_*() function. + * Dispatch rule to a corresponding match_*() function, invert result + * if rule contains the inequality condition and evaluate rule if it + * has appropriate action type. */ static bool -do_match(const TfwHttpReq *req, const TfwHttpMatchRule *rule) +do_eval(const TfwHttpReq *req, const TfwHttpMatchRule *rule) { match_fn match_fn; tfw_http_match_fld_t field; + bool matched; TFW_DBG2("rule: %p, field: %#x, op: %#x, arg:%d:%d'%.*s'\n", rule, rule->field, rule->op, rule->arg.type, rule->arg.len, rule->arg.len, rule->arg.str); BUG_ON(!req || !rule); - BUG_ON(rule->field < 0 || rule->field >= _TFW_HTTP_MATCH_F_COUNT); - BUG_ON(rule->op < 0 || rule->op >= _TFW_HTTP_MATCH_O_COUNT); - BUG_ON(rule->arg.type < 0 || rule->arg.type >= _TFW_HTTP_MATCH_A_COUNT); - BUG_ON(rule->arg.len <= 0 || rule->arg.len >= TFW_HTTP_MATCH_MAX_ARG_LEN); + BUG_ON(rule->field <= 0 || rule->field >= _TFW_HTTP_MATCH_F_COUNT); + BUG_ON(rule->op <= 0 || rule->op >= _TFW_HTTP_MATCH_O_COUNT); + BUG_ON(rule->act.type <= 0 || rule->act.type >= _TFW_HTTP_MATCH_ACT_COUNT); + BUG_ON(rule->arg.type <= 0 || rule->arg.type >= _TFW_HTTP_MATCH_A_COUNT); + BUG_ON(rule->arg.len < 0 || rule->arg.len >= TFW_HTTP_MATCH_MAX_ARG_LEN); field = rule->field; match_fn = match_fn_tbl[field]; BUG_ON(!match_fn); - return match_fn(req, rule); + matched = match_fn(req, rule); + if (rule->inv) + matched = !matched; + if (!matched) + return false; + /* + * Evaluate mark action. Set mark only for head skb here; propagating + * to others skb will take place later - in SS level. + */ + if (rule->act.type == TFW_HTTP_MATCH_ACT_MARK) { + req->msg.skb_head->mark = rule->act.mark; + return false; + } + return true; } /** @@ -378,14 +400,14 @@ do_match(const TfwHttpReq *req, const TfwHttpMatchRule *rule) * Return a first matching rule. */ TfwHttpMatchRule * -tfw_http_match_req(const TfwHttpReq *req, const TfwHttpMatchList *mlst) +tfw_http_match_req(const TfwHttpReq *req, struct list_head *mlst) { TfwHttpMatchRule *rule; TFW_DBG2("Matching request: %p, list: %p\n", req, mlst); - list_for_each_entry(rule, &mlst->list, list) { - if (do_match(req, rule)) + list_for_each_entry(rule, mlst, list) { + if (do_eval(req, rule)) return rule; } @@ -394,77 +416,139 @@ tfw_http_match_req(const TfwHttpReq *req, const TfwHttpMatchList *mlst) EXPORT_SYMBOL(tfw_http_match_req); /** - * Allocate a rule from the pool of @mlst and add it to the list. + * Allocate an empty HTTP chain. */ -TfwHttpMatchRule * -tfw_http_match_rule_new(TfwHttpMatchList *mlst, size_t arg_len) +TfwHttpChain * +tfw_http_chain_add(const char *name, TfwHttpTable *table) { - TfwHttpMatchRule *rule; - size_t size = TFW_HTTP_MATCH_RULE_SIZE(arg_len); + TfwHttpChain *chain; + int name_sz = name ? (strlen(name) + 1) : 0; + int size = sizeof(TfwHttpChain) + name_sz; - BUG_ON(!mlst || !mlst->pool); - - rule = tfw_pool_alloc(mlst->pool, size); - if (!rule) { - TFW_ERR_NL("Can't allocate a rule for match list: %p\n", mlst); + if (!(chain = tfw_pool_alloc(table->pool, size))) { + TFW_ERR("Can't allocate memory for HTTP chain\n"); return NULL; } - memset(rule, 0, size); - rule->arg.len = arg_len; - INIT_LIST_HEAD(&rule->list); - list_add_tail(&rule->list, &mlst->list); + memset(chain, 0, size); + INIT_LIST_HEAD(&chain->list); + INIT_LIST_HEAD(&chain->mark_list); + INIT_LIST_HEAD(&chain->match_list); + chain->pool = table->pool; - return rule; + if (name) { + chain->name = (char *)(chain + 1); + memcpy((void *)chain->name, (void *)name, name_sz); + } + + list_add(&chain->list, &table->head); + + return chain; } -EXPORT_SYMBOL(tfw_http_match_rule_new); +EXPORT_SYMBOL(tfw_http_chain_add); /** - * Allocate an empty list of rules. + * Free http table (together with all elements allocated from its pool). */ -TfwHttpMatchList * -tfw_http_match_list_alloc(void) +void +tfw_http_table_free(TfwHttpTable *table) { - TfwHttpMatchList *mlst; - - mlst = tfw_pool_new(TfwHttpMatchList, 0); - if (!mlst) { - TFW_ERR_NL("Can't create a memory pool\n"); - return NULL; - } - - INIT_LIST_HEAD(&mlst->list); - - return mlst; + if (table) + tfw_pool_destroy(table->pool); } -EXPORT_SYMBOL(tfw_http_match_list_alloc); +EXPORT_SYMBOL(tfw_http_table_free); /** - * Free a list of rules (together with all elements allocated from its pool). + * Allocate a rule from the pool of current http table + * and add it to @chain list. */ -void -tfw_http_match_list_free(TfwHttpMatchList *mlst) +TfwHttpMatchRule * +tfw_http_rule_new(TfwHttpChain *chain, tfw_http_match_arg_t type, + size_t arg_size) { - if (mlst) - tfw_pool_destroy(mlst->pool); + TfwHttpMatchRule *rule; + struct list_head *head; + size_t size = (type == TFW_HTTP_MATCH_A_STR) + ? TFW_HTTP_MATCH_RULE_SIZE(arg_size) + : sizeof(TfwHttpMatchRule); + + BUG_ON(!chain || !chain->pool); + if (!(rule = tfw_pool_alloc(chain->pool, size))) { + TFW_ERR_NL("Can't allocate a rule for http chain: %p\n", + chain->name); + return NULL; + } + + head = (type == TFW_HTTP_MATCH_A_NUM) + ? &chain->mark_list + : &chain->match_list; + memset(rule, 0, size); + INIT_LIST_HEAD(&rule->list); + list_add_tail(&rule->list, head); + + return rule; } -EXPORT_SYMBOL(tfw_http_match_list_free); +EXPORT_SYMBOL(tfw_http_rule_new); -void -tfw_http_match_rule_init(TfwHttpMatchRule *rule, tfw_http_match_fld_t field, - tfw_http_match_op_t op, tfw_http_match_arg_t type, - const char *arg) +int +tfw_http_rule_init(TfwHttpMatchRule *rule, tfw_http_match_fld_t field, + tfw_http_match_op_t op, tfw_http_match_arg_t type, + const char *arg, size_t arg_len) { rule->field = field; rule->op = op; rule->arg.type = type; - rule->arg.len = strlen(arg); - memcpy(rule->arg.str, arg, rule->arg.len + 1); + if (type == TFW_HTTP_MATCH_A_WILDCARD) + return 0; + + if (type == TFW_HTTP_MATCH_A_NUM) { + if (tfw_cfg_parse_uint(arg, &rule->arg.num)) { + TFW_ERR_NL("sched_http: invalid 'mark'" + " codition: '%s'\n", arg); + return -EINVAL; + } + return 0; + } + + rule->arg.len = arg_len; + memcpy(rule->arg.str, arg, arg_len); if (field == TFW_HTTP_MATCH_F_HDR_RAW) { char *p = rule->arg.str; while ((*p = tolower(*p))) p++; } + + return 0; +} +EXPORT_SYMBOL(tfw_http_rule_init); + +bool +tfw_http_arg_adjust(const char **arg_out, size_t *size_out, + tfw_http_match_op_t *op_out) +{ + size_t len; + const char *arg = *arg_out; + + BUG_ON(!arg); + len = strlen(arg); + *op_out = TFW_HTTP_MATCH_O_EQ; + *size_out = len + 1; + + if (arg[len - 1] == '*') { + if (len == 1) { + *op_out = TFW_HTTP_MATCH_O_WILDCARD; + return false; + } + *op_out = TFW_HTTP_MATCH_O_PREFIX; + --(*size_out); + } + if (arg[0] == '*') { + *op_out = TFW_HTTP_MATCH_O_SUFFIX; + *arg_out = &arg[1]; + --(*size_out); + } + + return true; } -EXPORT_SYMBOL(tfw_http_match_rule_init); +EXPORT_SYMBOL(tfw_http_arg_adjust); diff --git a/tempesta_fw/http_match.h b/tempesta_fw/http_match.h index 4faf879f9..15a1cbb2d 100644 --- a/tempesta_fw/http_match.h +++ b/tempesta_fw/http_match.h @@ -27,6 +27,26 @@ #include "pool.h" #include "http.h" +/** + * HTTP chain. Contains list of rules for matching. + */ +typedef struct { + struct list_head list; + struct list_head mark_list; + struct list_head match_list; + const char *name; + TfwPool *pool; +} TfwHttpChain; + +/** + * HTTP table. Contains list of HTTP chains. + */ +typedef struct { + struct list_head head; + bool chain_dflt; + TfwPool *pool; +} TfwHttpTable; + typedef enum { TFW_HTTP_MATCH_F_NA = 0, TFW_HTTP_MATCH_F_WILDCARD, @@ -37,6 +57,7 @@ typedef enum { TFW_HTTP_MATCH_F_HOST, TFW_HTTP_MATCH_F_METHOD, TFW_HTTP_MATCH_F_URI, + TFW_HTTP_MATCH_F_MARK, _TFW_HTTP_MATCH_F_COUNT } tfw_http_match_fld_t; @@ -61,20 +82,41 @@ typedef enum { _TFW_HTTP_MATCH_A_COUNT } tfw_http_match_arg_t; +typedef enum { + TFW_HTTP_MATCH_ACT_NA = 0, + TFW_HTTP_MATCH_ACT_CHAIN, + TFW_HTTP_MATCH_ACT_VHOST, + TFW_HTTP_MATCH_ACT_MARK, + TFW_HTTP_MATCH_ACT_BLOCK, + _TFW_HTTP_MATCH_ACT_COUNT +} tfw_http_rule_act_t; + typedef struct { tfw_http_match_arg_t type; short len; /* Actual amount of memory allocated for the union below. */ union { tfw_http_meth_t method; + unsigned int num; TfwAddr addr; char str[0]; }; } TfwHttpMatchArg; +typedef struct { + tfw_http_rule_act_t type; + union { + TfwHttpChain *chain; + TfwVhost *vhost; + unsigned int mark; + }; +} TfwHttpAction; + typedef struct { struct list_head list; tfw_http_match_fld_t field; /* Field of a HTTP message to compare. */ + bool inv; /* Comparison inversion (inequality) flag.*/ tfw_http_match_op_t op; /* Comparison operator. */ + TfwHttpAction act; /* Rule action. */ TfwHttpMatchArg arg; /* A value to be compared with the field. note: the @arg has variable length. */ } TfwHttpMatchRule; @@ -87,83 +129,41 @@ typedef struct { #define TFW_HTTP_MATCH_RULE_SIZE(arg_len) \ (offsetof(TfwHttpMatchRule, arg.str) + arg_len) -/** - * Size of a container of the TfwHttpMatchRule. - * - * @arg_len is variable size of the @arg member. - * Because of this, the rule must be the last member in the container. - */ -#define TFW_HTTP_MATCH_CONT_SIZE(container_struct_name, arg_len) \ - (sizeof(container_struct_name) - sizeof(TfwHttpMatchRule) \ - + TFW_HTTP_MATCH_RULE_SIZE(arg_len)) - -/** - * List of rules for matching. - */ -typedef struct { - struct list_head list; - TfwPool *pool; -} TfwHttpMatchList; - - -TfwHttpMatchList *tfw_http_match_list_alloc(void); -void tfw_http_match_list_free(TfwHttpMatchList *); +TfwHttpChain *tfw_http_chain_add(const char *name, TfwHttpTable *table); +void tfw_http_table_free(TfwHttpTable *table); /** - * Match a HTTP request agains a list of rules. + * Match a HTTP request agains a list of rules in chain. * Return a matching rule. */ -TfwHttpMatchRule *tfw_http_match_req(const TfwHttpReq *, const TfwHttpMatchList *); +TfwHttpMatchRule *tfw_http_match_req(const TfwHttpReq *req, + struct list_head *mlst); /** - * Allocate a new rule in a given list. + * Allocate a new rule in a given chain. */ -TfwHttpMatchRule *tfw_http_match_rule_new(TfwHttpMatchList *, size_t arg_len); +TfwHttpMatchRule *tfw_http_rule_new(TfwHttpChain *chain, + tfw_http_match_arg_t type, + size_t arg_len); -/** - * Match a HTTP request against a list of rules, but return a container - * structure instead of TfwHttpMatchRule. - */ -#define tfw_http_match_req_entry(req, mlst, container, member) \ -({ \ - container *_c = NULL; \ - TfwHttpMatchRule *_r = tfw_http_match_req((req), (mlst)); \ - if (_r) \ - _c = container_of(_r, container, member); \ - _c; \ -}) +int tfw_http_rule_init(TfwHttpMatchRule *rule, tfw_http_match_fld_t field, + tfw_http_match_op_t op, tfw_http_match_arg_t type, + const char *arg, size_t arg_len ); -/** - * Allocate a container (with embedded rule) within a rule list. - */ -#define tfw_http_match_entry_new(mlst, container, member, arg_len) \ -({ \ - size_t _s = TFW_HTTP_MATCH_CONT_SIZE(container, arg_len); \ - container *_c = tfw_pool_alloc((mlst)->pool, _s); \ - if (!_c) { \ - TFW_ERR("Can't allocate memory from pool\n"); \ - } else { \ - memset(_c, 0, _s); \ - INIT_LIST_HEAD(&_c->member.list); \ - list_add_tail(&_c->member.list, &(mlst)->list); \ - } \ - _c; \ -}) +bool tfw_http_arg_adjust(const char **arg_out, size_t *size_out, + tfw_http_match_op_t *op_out); -#define tfw_http_match_for_each(mlst, container, member, func) \ +#define tfw_http_chain_rules_for_each(chain, func) \ ({ \ int r = 0; \ TfwHttpMatchRule *rule; \ - if (mlst) \ - list_for_each_entry(rule, &mlst->list, list) { \ - r = func(container_of(rule, container, member));\ - if (r) \ - break; \ - } \ + if (chain) { \ + list_for_each_entry(rule, &(chain)->match_list, list) \ + r |= func(rule); \ + list_for_each_entry(rule, &(chain)->mark_list, list) \ + r |= func(rule); \ + } \ r; \ }) -void tfw_http_match_rule_init(TfwHttpMatchRule *rule, tfw_http_match_fld_t field, - tfw_http_match_op_t op, tfw_http_match_arg_t type, const char *arg); - #endif /* __TFW_HTTP_MATCH_H__ */ diff --git a/tempesta_fw/sched.c b/tempesta_fw/sched.c index d237d3c43..51cb39af0 100644 --- a/tempesta_fw/sched.c +++ b/tempesta_fw/sched.c @@ -51,7 +51,7 @@ static DEFINE_SPINLOCK(sched_lock); * stops when these schedulers run out. */ TfwVhost * -tfw_sched_get_vhost(TfwMsg *msg) +tfw_sched_get_vhost(TfwMsg *msg, bool *block) { TfwVhost *vhost; TfwScheduler *sched; @@ -60,7 +60,7 @@ tfw_sched_get_vhost(TfwMsg *msg) list_for_each_entry_rcu(sched, &sched_list, list) { if (!sched->sched_vhost) break; - if ((vhost = sched->sched_vhost(msg))) { + if ((vhost = sched->sched_vhost(msg, block))) { rcu_read_unlock(); return vhost; } diff --git a/tempesta_fw/sched/tfw_sched_http.c b/tempesta_fw/sched/tfw_sched_http.c index 76678a67a..775d1f66c 100644 --- a/tempesta_fw/sched/tfw_sched_http.c +++ b/tempesta_fw/sched/tfw_sched_http.c @@ -5,7 +5,8 @@ * * The goal of this module is to implement a load balancing scheme based on * HTTP message contents, that is, to provide user a way to route HTTP requests - * to different back-end server groups depending on HTTP request fields: + * to different virtual hosts locations for additional analizing and then - to + * different back-end server groups depending on HTTP request fields: * Host, URI, headers, etc. * * For example, you have a hardware setup that consists of: @@ -17,9 +18,10 @@ * scheduler module allows you to reach that. * * We utilize rule-based HTTP matching logic from http_match.c here. - * User defines a list of pattern-matching rules in a configuration file, - * and we match every request against all rules, and the first matching rule - * determines a back-end server to which the request is sent. + * User defines a namber for chains with lists of pattern-matching rules + * in a configuration file, and we match every request against all rules, + * in all linked chains of current HTTP table and the first matching rule + * determines a virtual host to which the request is redirected. * * The configuration section for the example above looks like: * srv_group webapp_site1 { @@ -33,24 +35,49 @@ * server 10.0.18.2; * server 10.0.18.3; * } - * sched_http_rules { - * match storage_servers uri prefix "static.example.com"; - * match webapp_site1 host eq "site1.example.com"; - * match webapp_site2 host eq "site2.example.com"; + * vhost storage { + * proxy_pass storage_servers; + * ... + * } + * vhost ws1 { + * proxy_pass webapp_site1; + * ... + * } + * vhost ws2 { + * proxy_pass webapp_site2; + * ... + * } + * http_chain base { + * ... + * mark != 1 -> storage; + * } + * http_chain { + * uri == "static.example.com*" -> base; + * host == "site1.example.com" -> ws1; + * host == "site2.example.com" -> ws2; * } * - * There's also a wildcard, or default match rule that looks like this: - * match storage_servers * * * - * Here all of field, op, and arg arguments of the rule are wilcard characters. - * This rule works as last resort option, and it forwards requests that didn't - * match any more specific rule to the designated server group. If no default - * rule is specified, it is created implicitly to point to the group 'default' - * if it exists. As all match rules are processed in sequential order, this - * rule must come last to serve the intended role. + * There's also a default match rule that looks like this: + * -> storage; + * This rule works as last resort option, and if specified it forwards requests + * that didn't match any more specific rule to the designated virtual host. As + * all match rules are processed in sequential order, this rule must come last + * to serve the intended role. * - * This module handles only the "sched_http_rules" section. It simply selects - * a "match" rule for an incoming HTTP request. Other entities ("server" and - * "srv_group") are handled in other modules. + * Rules are grouped in HTTP chains. One main HTTP chain (without name) must + * be specified after all other chains in configuration file. If no main chain + * is specified, it is created implicitly. In this case one default match rule + * pointing to default virtual host will be created in implicit main chain if + * default virtual host is present in configuration and if such default rule + * (with default virtual host) have not been specified explicitly in any chain + * in configuration. + * Besides, user can explicitly create main HTTP chain with empty list of rules, + * which means the complete absence of rules - all incoming requests will be + * dropped in such configuration. + * + * This module handles only the "http_chain" sections. It simply selects rule for + * an incoming HTTP request. Other entities ("server", "srv_group" and "vhost") + * are handled in other modules. * * TODO: * - Extended string matching operators: "regex", "substring". @@ -85,46 +112,77 @@ MODULE_DESCRIPTION("Tempesta HTTP scheduler"); MODULE_VERSION("0.3.1"); MODULE_LICENSE("GPL"); -typedef struct { - TfwVhost *vhost; - TfwHttpMatchRule rule; -} TfwSchedHttpRule; +/* Active HTTP table. */ +static TfwHttpTable __rcu *tfw_table; +/* Reconfig HTTP table. */ +static TfwHttpTable *tfw_table_reconfig; +/* Entry for configuration particular HTTP chain of rules. */ +static TfwHttpChain *tfw_chain_entry; + +/* + * Scan all rules in linked chains of current active HTTP table. Main HTTP + * chain is processed primarily (must be first in the table list); if rule + * of some chain points to other chain - move to that chain and scan it. + */ +static TfwVhost * +tfw_sched_http_table_scan(TfwMsg *msg, TfwHttpTable *table, bool *block) +{ + TfwHttpChain *chain; + TfwHttpMatchRule *rule; + + chain = list_first_entry_or_null(&table->head, TfwHttpChain, list); + BUG_ON(!chain || chain->name); + while (chain) { + rule = tfw_http_match_req((TfwHttpReq *)msg, &chain->mark_list); + if (!rule) + rule = tfw_http_match_req((TfwHttpReq *)msg, + &chain->match_list); + if (unlikely(!rule)) { + TFW_DBG("sched_http: No rule found in HTTP" + " chain '%s'\n", chain->name); + return NULL; + } + chain = (rule->act.type == TFW_HTTP_MATCH_ACT_CHAIN) + ? rule->act.chain + : NULL; + } + + /* If rule points to virtual host, return the pointer. */ + if (rule->act.type == TFW_HTTP_MATCH_ACT_VHOST) + return rule->act.vhost; -/* Active HTTP Scheduler rules. */ -static TfwHttpMatchList __rcu *tfw_rules; -/* Reconfig HTTP Scheduler rules. */ -static TfwHttpMatchList *tfw_rules_reconfig; + /* If rule has 'block' action, request must be blocked. */ + if (rule->act.type == TFW_HTTP_MATCH_ACT_BLOCK) + *block = true; + + return NULL; +} /* * Find vhost for an outgoing HTTP request. * - * The search is based on contents of an HTTP request and match rules - * that specify which Vhost the request should be forwarded to. + * The search is based on contents of an HTTP request and + * match http chain rules that specify which virtual host + * or other http chain the request should be forwarded to. */ static TfwVhost * -tfw_sched_http_sched_vhost(TfwMsg *msg) +tfw_sched_http_sched_vhost(TfwMsg *msg, bool *block) { - TfwSchedHttpRule *rule; - TfwHttpMatchList *active_rules; TfwVhost *vhost = NULL; + TfwHttpTable *active_table; rcu_read_lock_bh(); - active_rules = rcu_dereference_bh(tfw_rules); - if(!active_rules || list_empty(&active_rules->list)) + active_table = rcu_dereference_bh(tfw_table); + if(!active_table) goto done; - rule = tfw_http_match_req_entry((TfwHttpReq *)msg, active_rules, - TfwSchedHttpRule, rule); - if (unlikely(!rule)) { - TFW_DBG("sched_http: No matching rule found.\n"); - goto done; - } - vhost = rule->vhost; - BUG_ON(!vhost); + BUG_ON(list_empty(&active_table->head)); + if ((vhost = tfw_sched_http_table_scan(msg, active_table, block))) + tfw_vhost_get(vhost); done: - rcu_read_unlock_bh(); - return vhost; + rcu_read_unlock_bh(); + return vhost; } static TfwSrvConn * @@ -165,26 +223,16 @@ static TfwScheduler tfw_sched_http = { /* e.g.: match group ENUM eq "pattern"; */ static const TfwCfgEnum tfw_sched_http_cfg_field_enum[] = { - { "*", TFW_HTTP_MATCH_F_WILDCARD }, { "uri", TFW_HTTP_MATCH_F_URI }, { "host", TFW_HTTP_MATCH_F_HOST }, { "hdr_host", TFW_HTTP_MATCH_F_HDR_HOST }, { "hdr_conn", TFW_HTTP_MATCH_F_HDR_CONN }, { "hdr_ref", TFW_HTTP_MATCH_F_HDR_REFERER }, + { "mark", TFW_HTTP_MATCH_F_MARK }, { "hdr_raw", TFW_HTTP_MATCH_F_HDR_RAW }, {} }; -/* e.g.: match group uri ENUM "pattern"; */ -static const TfwCfgEnum tfw_sched_http_cfg_op_enum[] = { - { "*", TFW_HTTP_MATCH_O_WILDCARD }, - { "eq", TFW_HTTP_MATCH_O_EQ }, - { "prefix", TFW_HTTP_MATCH_O_PREFIX }, - { "suffix", TFW_HTTP_MATCH_O_SUFFIX }, - /* TODO: substr, regex, case sensitive/insensitive versions. */ - {} -}; - static const tfw_http_match_arg_t tfw_sched_http_cfg_arg_tbl[_TFW_HTTP_MATCH_F_COUNT] = { [TFW_HTTP_MATCH_F_WILDCARD] = TFW_HTTP_MATCH_A_WILDCARD, @@ -195,200 +243,297 @@ tfw_sched_http_cfg_arg_tbl[_TFW_HTTP_MATCH_F_COUNT] = { [TFW_HTTP_MATCH_F_HOST] = TFW_HTTP_MATCH_A_STR, [TFW_HTTP_MATCH_F_METHOD] = TFW_HTTP_MATCH_A_METHOD, [TFW_HTTP_MATCH_F_URI] = TFW_HTTP_MATCH_A_STR, + [TFW_HTTP_MATCH_F_MARK] = TFW_HTTP_MATCH_A_NUM, }; +static TfwHttpChain * +tfw_chain_lookup(const char *name) +{ + TfwHttpChain *chain; + + list_for_each_entry(chain, &tfw_table_reconfig->head, list) { + if (chain->name && !strcasecmp(chain->name, name)) + return chain; + } + return NULL; +} + +static inline bool +tfw_http_rule_default_exist(struct list_head *head) +{ + TfwHttpMatchRule *rule; + + rule = !list_empty(head) + ? list_last_entry(head, TfwHttpMatchRule, list) + : NULL; + + if (rule + && !rule->inv + && rule->field == TFW_HTTP_MATCH_F_WILDCARD + && rule->act.type != TFW_HTTP_MATCH_ACT_MARK) + return true; + + return false; +} + +static int +tfw_sched_http_cfgstart(void) +{ + BUG_ON(tfw_table_reconfig); + + tfw_table_reconfig = tfw_pool_new(TfwHttpTable, TFW_POOL_ZERO); + if (!tfw_table_reconfig) { + TFW_ERR_NL("Can't create a memory pool\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&tfw_table_reconfig->head); + + return 0; +} + /** - * Handle the "sched_http_rules" section. - * Allocate the tfw_sched_http_rules list. All nested rules are added to the list. + * Handle the "http_chain" section. Allocate the chain inside reconfig table. + * All nested rules are added to the chain. */ static int -tfw_cfgop_sched_http_rules_begin(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_sched_http_chain_begin(TfwCfgSpec *cs, TfwCfgEntry *ce) { - TFW_DBG("sched_http: begin sched_http_rules\n"); + TfwHttpChain *chain; + const char *name = NULL; + + BUG_ON(!tfw_table_reconfig); + BUG_ON(tfw_chain_entry); + + TFW_DBG("sched_http: begin http_chain\n"); + + if (ce->val_n > 1) { + TFW_ERR_NL("Invalid number of arguments: %zu\n", ce->val_n); + return -EINVAL; + } + if (ce->attr_n) { + TFW_ERR_NL("Unexpected attributes\n"); + return -EINVAL; + } + + chain = list_first_entry_or_null(&tfw_table_reconfig->head, + TfwHttpChain, list); + if (chain && !chain->name) { + TFW_ERR_NL("Main HTTP chain must be only one and last\n"); + return -EINVAL; + } + if (ce->val_n) { + name = ce->vals[0]; + list_for_each_entry(chain, &tfw_table_reconfig->head, list) { + if (!strcasecmp(chain->name, name)) { + TFW_ERR_NL("Duplicate http chain" + " entry: '%s'\n", name); + return -EINVAL; + } + } + } - if (!tfw_rules_reconfig) - tfw_rules_reconfig = tfw_http_match_list_alloc(); - if (!tfw_rules_reconfig) + tfw_chain_entry = tfw_http_chain_add(name, tfw_table_reconfig); + if (!tfw_chain_entry) return -ENOMEM; return 0; } static int -tfw_cfgop_sched_http_rules_finish(TfwCfgSpec *cs) +tfw_cfgop_sched_http_chain_finish(TfwCfgSpec *cs) { - TFW_DBG("sched_http: finish sched_http_rules\n"); - BUG_ON(!tfw_rules_reconfig); + TFW_DBG("sched_http: finish http_chain\n"); + BUG_ON(!tfw_chain_entry); + tfw_chain_entry = NULL; return 0; } /** - * Handle a "match" entry within "sched_http_rules" section, e.g.: - * sched_http_rules { - * match vhost1 uri prefix "/foo"; - * match vhost2 host eq "example.com"; + * Handle a rule entry within "http_chain" section, e.g.: + * http_chain { + * uri == "*.php" -> static; + * mark == 2 -> waf_chain; + * referer != "*hacked.com" -> mark = 7; + * -> mark = 3; * } * - * This callback is invoked for every such "match" entry. - * It resolves name of the vhost, parses the rule and adds the entry to the - * tfw_sched_http_rules list. + * This callback is invoked for every such rule. It interprets the + * condition and action parts of the rule, and adds the entry to + * the list of rules of current chain. * * Syntax: - * +------------------------ a reference to "vhost"; - * | +------------------ HTTP request field - * | | +------------ operator (eq, prefix, substr, etc) - * | | | +---- argument for the operator (any string) - * | | | | - * V V V V - * match vhost3 uri prefix "/foo/bar/baz.html" + * +------------------------ First operand of rule's conition part + * | (HTTP request field or 'mark'); + * | +-------------------- Condition type: equal ('==') or not + * | | equal ('!='); + * | | +-------------- Second operand of rule's condition part + * | | | (argument for the rule - any string); + * | | | +---- Action part of the rule (reference to + * | | | | other http_chain or vhost, 'block' or + * | | | | 'mark' action). + * V V V V + * uri == "*.php" -> static * */ static int -tfw_cfgop_match(TfwCfgSpec *cs, TfwCfgEntry *e) +tfw_cfgop_http_rule(TfwCfgSpec *cs, TfwCfgEntry *e) { int r; - size_t arg_size; - TfwSchedHttpRule *rule; - tfw_http_match_op_t op; - tfw_http_match_fld_t field; - tfw_http_match_arg_t type; - TfwVhost *vhost; - const char *in_vhost, *in_field, *in_op, *in_arg; - - if ((r = tfw_cfg_check_val_n(e, 4))) - return r; - - in_vhost = e->vals[0]; - in_field = e->vals[1]; - in_op = e->vals[2]; - in_arg = e->vals[3]; + bool invert; + TfwHttpMatchRule *rule; + const char *in_field, *arg, *action, *action_val; + tfw_http_match_op_t op = TFW_HTTP_MATCH_O_WILDCARD; + tfw_http_match_fld_t field = TFW_HTTP_MATCH_F_WILDCARD; + tfw_http_match_arg_t type = TFW_HTTP_MATCH_A_WILDCARD; + TfwCfgRule *cfg_rule = &e->rule; + size_t arg_size = 0; + TfwHttpChain *chain = NULL; + TfwVhost *vhost = NULL; - vhost = tfw_vhost_lookup(in_vhost); - if (!vhost) { - TFW_ERR_NL("sched_http: vhost is not found: '%s'\n", - in_vhost); - r = -EINVAL; - goto err; + BUG_ON(!tfw_chain_entry); + if ((r = tfw_cfg_check_val_n(e, 0))) + return r; + if (e->attr_n) { + TFW_ERR_NL("Attibutes count must be zero\n"); + return -EINVAL; } - r = tfw_cfg_map_enum(tfw_sched_http_cfg_field_enum, in_field, &field); - if (r) { - TFW_ERR_NL("sched_http: invalid HTTP request field: '%s'\n", - in_field); - r = -EINVAL; - goto err; + invert = cfg_rule->inv; + in_field = cfg_rule->fst; + arg = cfg_rule->snd; + action = cfg_rule->act; + action_val = cfg_rule->val; + BUG_ON(!action); + + if (tfw_http_rule_default_exist(&tfw_chain_entry->match_list)) { + TFW_ERR_NL("sched_http: default HTTP rule must be" + " only one and last; chain '%s'\n", + tfw_chain_entry->name ? : "main" ); + return -EINVAL; } - r = tfw_cfg_map_enum(tfw_sched_http_cfg_op_enum, in_op, &op); - if (r) { - TFW_ERR_NL("sched_http: invalid matching operator: '%s'\n", - in_op); - r = -EINVAL; - goto err; + /* Interpret condition part of the rule. */ + if (in_field && tfw_http_arg_adjust(&arg, &arg_size, &op)) { + r = tfw_cfg_map_enum(tfw_sched_http_cfg_field_enum, + in_field, &field); + if (r) { + TFW_ERR_NL("sched_http: invalid rule field: '%s'\n", + in_field); + return r; + } + type = tfw_sched_http_cfg_arg_tbl[field]; } - - arg_size = strlen(in_arg) + 1; - type = tfw_sched_http_cfg_arg_tbl[field]; - - rule = tfw_http_match_entry_new(tfw_rules_reconfig, - TfwSchedHttpRule, rule, arg_size); + rule = tfw_http_rule_new(tfw_chain_entry, type, arg_size); if (!rule) { - TFW_ERR_NL("sched_http: can't allocate memory for parsed " - "rule\n"); - r = -ENOMEM; - goto err; - } - - TFW_DBG("sched_http: parsed rule: match '%s'=%p '%s'=%d '%s'=%d '%s'\n", - in_vhost, vhost, in_field, field, in_op, op, in_arg); - - if (type == TFW_HTTP_MATCH_A_STR || type == TFW_HTTP_MATCH_A_WILDCARD) { - tfw_http_match_rule_init(&rule->rule, field, op, type, in_arg); + TFW_ERR_NL("sched_http: can't allocate memory for rule\n"); + return -ENOMEM; + } else if (type == TFW_HTTP_MATCH_A_STR || + type == TFW_HTTP_MATCH_A_NUM || + type == TFW_HTTP_MATCH_A_WILDCARD) + { + rule->inv = invert; + r = tfw_http_rule_init(rule, field, op, type, arg, arg_size - 1); + if (r) + return r; } else { BUG(); - // TODO: parsing not string matching rules + // TODO: parsing rules of other types } - rule->vhost = vhost; - - return 0; -err: - tfw_vhost_put(vhost); - - return r; -} -static int -tfw_cfgop_match_new(TfwCfgSpec *cs, TfwCfgEntry *e) -{ - int r; - TfwCfgRule *rule = &e->rule; - - if ((r = tfw_cfg_check_val_n(e, 0))) - return r; + /* Interpret action part of the rule. */ + if (!strcasecmp(action, "mark")) { + if (!action_val || + tfw_cfg_parse_uint(action_val,&rule->act.mark)) + { + TFW_ERR_NL("sched_http: 'mark' action must have" + " unsigned integer value: '%s'\n", + action_val); + return -EINVAL; + } + rule->act.type = TFW_HTTP_MATCH_ACT_MARK; + } else if (action_val) { + TFW_ERR_NL("sched_http: not 'mark' actions must not have" + " any value: '%s'\n", action_val); + return -EINVAL; + } else if (!strcasecmp(action, "block")) { + rule->act.type = TFW_HTTP_MATCH_ACT_BLOCK; + } else if ((chain = tfw_chain_lookup(action))) { + rule->act.type = TFW_HTTP_MATCH_ACT_CHAIN; + rule->act.chain = chain; + } else if ((vhost = tfw_vhost_lookup(action))) { + rule->act.type = TFW_HTTP_MATCH_ACT_VHOST; + rule->act.vhost = vhost; + } else { + TFW_ERR_NL("sched_http: neither http_chain nor vhost with" + " specified name were found: '%s'\n", action); + return -EINVAL; + } - if (e->attr_n) { - TFW_ERR_NL("Attibutes count must be zero\n"); + if (chain == tfw_chain_entry) { + TFW_ERR_NL("sched_http: cyclic reference of http_chain to" + " itself: '%s'\n", tfw_chain_entry->name); return -EINVAL; } - TFW_WARN_NL("TEST1!!! 'tfw_cfgop_match_new()' -> rule->fst = '%s'\n", rule->fst); - TFW_WARN_NL("TEST3!!! 'tfw_cfgop_match_new()' -> rule->snd = '%s'\n", rule->snd); - TFW_WARN_NL("TEST4!!! 'tfw_cfgop_match_new()' -> rule->act = '%s'\n", rule->act); - TFW_WARN_NL("TEST5!!! 'tfw_cfgop_match_new()' -> rule->val = '%s'\n", rule->val); + if (vhost && !strcasecmp(vhost->name, TFW_VH_DFT_NAME)) + tfw_table_reconfig->chain_dflt = true; return 0; } -tfw_cfgop_release_vhost(TfwSchedHttpRule *rule) +static int +tfw_cfgop_release_rule(TfwHttpMatchRule *rule) { - tfw_vhost_put(rule->vhost); + if (rule->act.type == TFW_HTTP_MATCH_ACT_VHOST) + tfw_vhost_put(rule->act.vhost); return 0; } static void -tfw_cfgop_free_rules(TfwHttpMatchList *rules) +tfw_cfgop_free_table(TfwHttpTable *table) { - if (!rules) + TfwHttpChain *chain; + + if (!table) return; - tfw_http_match_for_each(rules, TfwSchedHttpRule, rule, - tfw_cfgop_release_vhost); - tfw_http_match_list_free(rules); + list_for_each_entry(chain, &table->head, list) { + tfw_http_chain_rules_for_each(chain, tfw_cfgop_release_rule); + } + tfw_http_table_free(table); } static void -tfw_cfgop_replace_active_rules(TfwHttpMatchList *new_rules) +tfw_cfgop_replace_active_table(TfwHttpTable *new_table) { - TfwHttpMatchList *active_rules = tfw_rules; + TfwHttpTable *active_table = tfw_table; - rcu_assign_pointer(tfw_rules, new_rules); + rcu_assign_pointer(tfw_table, new_table); synchronize_rcu_bh(); - tfw_cfgop_free_rules(active_rules); + tfw_cfgop_free_table(active_table); } /** - * Delete all rules parsed out of the "sched_http_rules" section. + * Delete all rules parsed out of all "http_chain" sections for current (if + * this is not live reconfiguration) and reconfig HTTP tables. */ static void tfw_cfgop_cleanup_rules(TfwCfgSpec *cs) { - tfw_cfgop_free_rules(tfw_rules_reconfig); - tfw_rules_reconfig = NULL; + tfw_cfgop_free_table(tfw_table_reconfig); + tfw_table_reconfig = NULL; if (!tfw_runstate_is_reconfig()) - tfw_cfgop_replace_active_rules(NULL); + tfw_cfgop_replace_active_table(NULL); } -/* Forward declaration */ -static TfwMod tfw_sched_http_mod; -static TfwCfgSpec tfw_sched_http_rules_specs[]; - static int tfw_sched_http_start(void) { - tfw_cfgop_replace_active_rules(tfw_rules_reconfig); - tfw_rules_reconfig = NULL; + tfw_cfgop_replace_active_table(tfw_table_reconfig); + tfw_table_reconfig = NULL; return 0; } @@ -396,104 +541,99 @@ tfw_sched_http_start(void) static int tfw_sched_http_cfgend(void) { - TfwHttpMatchRule *mrule; - TfwVhost *vhost_dflt; - struct list_head mod_list; - TfwMod sched_mod; - TfwCfgSpec *cfg_spec; - static const char cfg_text[] = - "sched_http_rules {\nmatch default * * *;\n}\n"; int r; + TfwVhost *vhost_dflt; + TfwHttpChain *chain; + TfwHttpMatchRule *rule; /* - * See if we need to add a default rule that forwards all - * requests that do not match any rule to group 'default'. - * - * If there's a default rule already then we are all set. - */ - if (tfw_rules_reconfig && !list_empty(&tfw_rules_reconfig->list)) { - mrule = list_entry(tfw_rules_reconfig->list.prev, - TfwHttpMatchRule, list); - if ((mrule->field == TFW_HTTP_MATCH_F_WILDCARD) - && (mrule->op == TFW_HTTP_MATCH_O_WILDCARD) - && (mrule->arg.len == 1) && (*mrule->arg.str == '*')) - return 0; - } - /* - * No default rule specified in the configuration. If there is not - * default server group (and сonsequently, there's no default vhost), - * we have nowhere to point this rule to, so we needn't to create - * default rule at all. + * Add rule with 'default' virtual host into main HTTP chain if such + * rule have not been specified in any chain at all and if 'default' + * virtual host (explicit or implicit) is present in configuration + * and if main HTTP chain is absent. In any case - add empty main + * HTTP chain if it is absent. */ - if (!(vhost_dflt = tfw_vhost_lookup("default"))) + BUG_ON(!tfw_table_reconfig); + chain = list_first_entry_or_null(&tfw_table_reconfig->head, + TfwHttpChain, list); + if (chain && !chain->name) return 0; - tfw_vhost_put(vhost_dflt); - /* - * Add a default rule that points to group 'default'. Note that - * there's a restriction that 'sched_http_rules' option can only - * be seen once in the configuration file. As we know for sure - * the there's no default rule yet, we can work around that by - * removing the restriction in a copy of specs for the option. - * Using configuration processing functions, we add the default - * rule exactly the same way it would have been done if it were - * in the configuration file. - */ - sched_mod = tfw_sched_http_mod; - INIT_LIST_HEAD(&sched_mod.list); - INIT_LIST_HEAD(&mod_list); - list_add(&sched_mod.list, &mod_list); + if (!(chain = tfw_http_chain_add(NULL, tfw_table_reconfig))) + return -ENOMEM; + + if (tfw_table_reconfig->chain_dflt) + return 0; + + if (!(vhost_dflt = tfw_vhost_lookup(TFW_VH_DFT_NAME))) + return 0; - cfg_spec = tfw_cfg_spec_find(sched_mod.specs, "sched_http_rules"); - cfg_spec->allow_repeat = true; + rule = tfw_http_rule_new(chain, TFW_HTTP_MATCH_A_WILDCARD, 0); + if (!rule) { + TFW_ERR_NL("sched_http: can't allocate memory for" + " default rule of main HTTP chain\n"); + r = -ENOMEM; + goto err; + } + r = tfw_http_rule_init(rule, TFW_HTTP_MATCH_F_WILDCARD, + TFW_HTTP_MATCH_O_WILDCARD, + TFW_HTTP_MATCH_A_WILDCARD, + NULL, 0); + if (r) + goto err; - r = tfw_cfg_parse_mods(cfg_text, &mod_list); - cfg_spec->allow_repeat = false; + rule->act.type = TFW_HTTP_MATCH_ACT_VHOST; + rule->act.vhost = vhost_dflt; + return 0; +err: + tfw_vhost_put(vhost_dflt); return r; } +void +tfw_sched_http_cfgclean(void) { + tfw_cfgop_free_table(tfw_table_reconfig); + tfw_table_reconfig = NULL; +} + static TfwCfgSpec tfw_sched_http_rules_specs[] = { - { - .name = "match", - .deflt = NULL, - .handler = tfw_cfgop_match, - .allow_repeat = true, - .allow_reconfig = true, - }, { .name = TFW_CFG_RULE_NAME, .deflt = NULL, - .handler = tfw_cfgop_match_new, + .handler = tfw_cfgop_http_rule, .allow_none = true, .allow_repeat = true, .allow_reconfig = true, }, - {} + { 0 } }; static TfwCfgSpec tfw_sched_http_specs[] = { { - .name = "sched_http_rules", + .name = "http_chain", .deflt = NULL, .handler = tfw_cfg_handle_children, .cleanup = tfw_cfgop_cleanup_rules, .dest = tfw_sched_http_rules_specs, .spec_ext = &(TfwCfgSpecChild) { - .begin_hook = tfw_cfgop_sched_http_rules_begin, - .finish_hook = tfw_cfgop_sched_http_rules_finish + .begin_hook = tfw_cfgop_sched_http_chain_begin, + .finish_hook = tfw_cfgop_sched_http_chain_finish }, .allow_none = true, + .allow_repeat = true, .allow_reconfig = true, }, { 0 } }; static TfwMod tfw_sched_http_mod = { - .name = "tfw_sched_http", - .cfgend = tfw_sched_http_cfgend, - .start = tfw_sched_http_start, - .specs = tfw_sched_http_specs, + .name = "tfw_sched_http", + .cfgstart = tfw_sched_http_cfgstart, + .cfgend = tfw_sched_http_cfgend, + .start = tfw_sched_http_start, + .cfgclean = tfw_sched_http_cfgclean, + .specs = tfw_sched_http_specs, }; /* @@ -525,7 +665,7 @@ tfw_sched_http_exit(void) { TFW_DBG("sched_http: exit\n"); - BUG_ON(tfw_rules_reconfig); + BUG_ON(tfw_table_reconfig); tfw_sched_unregister(&tfw_sched_http); tfw_mod_unregister(&tfw_sched_http_mod); } diff --git a/tempesta_fw/server.h b/tempesta_fw/server.h index 149ce0e9e..3c5c06a0c 100644 --- a/tempesta_fw/server.h +++ b/tempesta_fw/server.h @@ -192,7 +192,7 @@ struct tfw_scheduler_t { void (*del_grp)(TfwSrvGroup *sg); int (*add_srv)(TfwServer *srv); void (*del_srv)(TfwServer *srv); - struct tfw_vhost_t *(*sched_vhost)(TfwMsg *msg); + struct tfw_vhost_t *(*sched_vhost)(TfwMsg *msg, bool *block); TfwSrvConn *(*sched_sg_conn)(TfwMsg *msg, TfwSrvGroup *sg); TfwSrvConn *(*sched_srv_conn)(TfwMsg *msg, TfwServer *srv); void (*sched_refcnt)(bool get); @@ -345,7 +345,7 @@ tfw_sg_name_match(TfwSrvGroup *sg, const char *name, unsigned int len) } /* Scheduler routines. */ -struct tfw_vhost_t *tfw_sched_get_vhost(TfwMsg *msg); +struct tfw_vhost_t *tfw_sched_get_vhost(TfwMsg *msg, bool *block); TfwScheduler *tfw_sched_lookup(const char *name); void tfw_sched_refcnt_all(bool get); int tfw_sched_register(TfwScheduler *sched); diff --git a/tempesta_fw/sock.c b/tempesta_fw/sock.c index 73443f657..c91f7eb13 100644 --- a/tempesta_fw/sock.c +++ b/tempesta_fw/sock.c @@ -335,7 +335,8 @@ ss_do_send(struct sock *sk, struct sk_buff **skb_head, int flags) struct tcp_sock *tp = tcp_sk(sk); struct sk_buff *skb; int size, mss = tcp_send_mss(sk, &size, MSG_DONTWAIT); - + unsigned int mark = (*skb_head)->mark; + TFW_DBG3("[%d]: %s: sk=%p queue_empty=%d send_head=%p" " sk_state=%d mss=%d size=%d\n", smp_processor_id(), __func__, @@ -364,9 +365,12 @@ ss_do_send(struct sock *sk, struct sk_buff **skb_head, int flags) ss_skb_init_for_xmit(skb); - TFW_DBG3("[%d]: %s: entail skb=%p data_len=%u len=%u\n", - smp_processor_id(), __func__, - skb, skb->data_len, skb->len); + /* Propagate mark of message head skb.*/ + skb->mark = mark; + + TFW_DBG3("[%d]: %s: entail skb=%p data_len=%u len=%u mark=%u\n", + smp_processor_id(), __func__, skb, + skb->data_len, skb->len, skb->mark); skb_entail(sk, skb); diff --git a/tempesta_fw/t/unit/test_http_match.c b/tempesta_fw/t/unit/test_http_match.c index 35bf0e977..cefe97425 100644 --- a/tempesta_fw/t/unit/test_http_match.c +++ b/tempesta_fw/t/unit/test_http_match.c @@ -32,15 +32,67 @@ typedef struct { TfwHttpMatchRule rule; } MatchEntry; -TfwHttpMatchList *test_mlst; +TfwHttpTable *test_table; +TfwHttpChain *test_chain; TfwHttpReq *test_req; +/** + * Size of a container of the TfwHttpMatchRule. + * + * @arg_len is variable size of the @arg member. + * Because of this, the rule must be the last member in the container. + */ +#define TFW_HTTP_MATCH_CONT_SIZE(container_struct_name, arg_len) \ + (sizeof(container_struct_name) - sizeof(TfwHttpMatchRule) \ + + TFW_HTTP_MATCH_RULE_SIZE(arg_len)) + +/** + * Allocate a container (with embedded rule) and add it to appropriate + * chain list (for numerical mark comparing or string matching). + */ +#define test_rule_container_new(chain, container, member, type, arg_len)\ +({ \ + size_t _s = (type == TFW_HTTP_MATCH_A_STR) \ + ? TFW_HTTP_MATCH_CONT_SIZE(container, arg_len) \ + : sizeof(container); \ + container *_c = tfw_pool_alloc((chain)->pool, _s); \ + if (!_c) { \ + TFW_ERR("Can't allocate memory from pool\n"); \ + } else { \ + struct list_head *head = (type == TFW_HTTP_MATCH_A_NUM) \ + ? &(chain)->mark_list \ + : &(chain)->match_list; \ + memset(_c, 0, _s); \ + INIT_LIST_HEAD(&_c->member.list); \ + list_add_tail(&_c->member.list, head); \ + } \ + _c; \ +}) + +/** + * Match a HTTP request against list of rules in chain, but return + * a container structure instead of TfwHttpMatchRule. + */ +#define test_rule_container_match_req(req, mlst, container, member) \ +({ \ + container *_c = NULL; \ + TfwHttpMatchRule *_r = tfw_http_match_req((req), (mlst)); \ + if (_r) \ + _c = container_of(_r, container, member); \ + _c; \ +}) + static void http_match_suite_setup(void) { test_req = test_req_alloc(1); - test_mlst = tfw_http_match_list_alloc(); - BUG_ON(!test_mlst); + + test_table = tfw_pool_new(TfwHttpTable, TFW_POOL_ZERO); + BUG_ON(!test_table); + INIT_LIST_HEAD(&test_table->head); + + test_chain = tfw_http_chain_add(NULL, test_table); + BUG_ON(!test_chain); } static void @@ -49,28 +101,37 @@ http_match_suite_teardown(void) test_req_free(test_req); test_req = NULL; - tfw_http_match_list_free(test_mlst); - test_mlst = NULL; + tfw_http_table_free(test_table); + test_table = NULL; } static void -test_mlst_add(int test_id, tfw_http_match_fld_t field, - tfw_http_match_op_t op, const char *arg) +test_chain_add_rule_str(int test_id, tfw_http_match_fld_t field, const char *arg) { MatchEntry *e; - size_t arg_size = strlen(arg) + 1; - - e = tfw_http_match_entry_new(test_mlst, MatchEntry, rule, arg_size); - tfw_http_match_rule_init(&e->rule, field, op, TFW_HTTP_MATCH_A_STR, arg); + tfw_http_match_op_t op; + size_t arg_size; + + tfw_http_arg_adjust(&arg, &arg_size, &op); + BUG_ON(field == TFW_HTTP_MATCH_F_WILDCARD + || op == TFW_HTTP_MATCH_O_WILDCARD); + + e = test_rule_container_new(test_chain, MatchEntry, rule, + TFW_HTTP_MATCH_A_STR, arg_size); + tfw_http_rule_init(&e->rule, field, op, TFW_HTTP_MATCH_A_STR, + arg, arg_size - 1); + /* Just dummy action type to avoid BUG_ON in 'do_eval()'. */ + e->rule.act.type = TFW_HTTP_MATCH_ACT_CHAIN; e->test_id = test_id; } int -test_mlst_match(void) +test_chain_match(void) { MatchEntry *e; - e = tfw_http_match_req_entry(test_req, test_mlst, MatchEntry, rule); + e = test_rule_container_match_req(test_req, &test_chain->match_list, + MatchEntry, rule); if (e) return e->test_id; @@ -89,10 +150,11 @@ TEST(tfw_http_match_req, returns_first_matching_rule) const TfwHttpMatchRule *match; TfwHttpMatchRule *r1, *r2, *r3; - r1 = tfw_http_match_rule_new(test_mlst, sizeof(r1->arg.method)); - r2 = tfw_http_match_rule_new(test_mlst, sizeof(r2->arg.method)); - r3 = tfw_http_match_rule_new(test_mlst, sizeof(r3->arg.method)); + r1 = tfw_http_rule_new(test_chain, TFW_HTTP_MATCH_A_METHOD, 0); + r2 = tfw_http_rule_new(test_chain, TFW_HTTP_MATCH_A_METHOD, 0); + r3 = tfw_http_rule_new(test_chain, TFW_HTTP_MATCH_A_METHOD, 0); + r1->act.type = r2->act.type = r3->act.type = TFW_HTTP_MATCH_ACT_CHAIN; r1->field = r2->field = r3->field = TFW_HTTP_MATCH_F_METHOD; r1->op = r2->op = r3->op = TFW_HTTP_MATCH_O_EQ; r1->arg.type = r2->arg.type = r3->arg.type = TFW_HTTP_MATCH_A_METHOD; @@ -102,7 +164,7 @@ TEST(tfw_http_match_req, returns_first_matching_rule) test_req->method = TFW_HTTP_METH_GET; - match = tfw_http_match_req(test_req, test_mlst); + match = tfw_http_match_req(test_req, &test_chain->match_list); EXPECT_EQ(r2, match); } @@ -111,27 +173,24 @@ TEST(http_match, uri_prefix) { int match_id; - test_mlst_add(1, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_PREFIX, - "/foo/bar/baz"); - test_mlst_add(2, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_PREFIX, - "/foo/ba"); - test_mlst_add(3, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_PREFIX, - "/"); + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_URI, "/foo/bar/baz*"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_URI, "/foo/ba*"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_URI, "/*"); set_tfw_str(&test_req->uri_path, "/foo/bar/baz.html"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(1, match_id); set_tfw_str(&test_req->uri_path, "/foo/bar/"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(2, match_id); set_tfw_str(&test_req->uri_path, "/baz"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(3, match_id); set_tfw_str(&test_req->uri_path, "../foo"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); } @@ -139,46 +198,39 @@ TEST(http_match, uri_suffix) { int match_id; - test_mlst_add(1, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_SUFFIX, - ".jpg"); - test_mlst_add(2, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_SUFFIX, - "/people.html"); - test_mlst_add(3, TFW_HTTP_MATCH_F_URI, TFW_HTTP_MATCH_O_SUFFIX, - "/bar/folks.html"); + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_URI, "*.jpg"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_URI, "*/people.html"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_URI, "*/bar/folks.html"); set_tfw_str(&test_req->uri_path, "/foo/bar/picture.jpg"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(1, match_id); set_tfw_str(&test_req->uri_path, "/foo/bar/people.html"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(2, match_id); set_tfw_str(&test_req->uri_path, "/foo/bar/folks.html"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(3, match_id); set_tfw_str(&test_req->uri_path, "../foo"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); set_tfw_str(&test_req->uri_path, "/foo/bar/picture.png"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); } TEST(http_match, host_eq) { int match_id; - test_mlst_add(1, TFW_HTTP_MATCH_F_HOST, TFW_HTTP_MATCH_O_EQ, - "www.natsys-lab.com"); - test_mlst_add(2, TFW_HTTP_MATCH_F_HOST, TFW_HTTP_MATCH_O_EQ, - "natsys-lab"); - test_mlst_add(3, TFW_HTTP_MATCH_F_HOST, TFW_HTTP_MATCH_O_EQ, - "NATSYS-LAB.COM"); - + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_HOST, "www.natsys-lab.com"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_HOST, "natsys-lab"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_HOST, "NATSYS-LAB.COM"); set_tfw_str(&test_req->host, "natsys-lab.com"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(3, match_id); } @@ -186,21 +238,21 @@ TEST(http_match, headers_eq) { int match_id; - test_mlst_add(1, TFW_HTTP_MATCH_F_HDR_RAW, TFW_HTTP_MATCH_O_EQ, - "User-Agent: U880D/4.0 (CP/M; 8-bit)"); - test_mlst_add(2, TFW_HTTP_MATCH_F_HDR_RAW, TFW_HTTP_MATCH_O_EQ, - "Connection: close"); - test_mlst_add(3, TFW_HTTP_MATCH_F_HDR_RAW, TFW_HTTP_MATCH_O_EQ, - "Connection: Keep-Alive"); + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_HDR_RAW, + "User-Agent: U880D/4.0 (CP/M; 8-bit)"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_HDR_RAW, + "Connection: close"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_HDR_RAW, + "Connection: Keep-Alive"); set_tfw_str(&test_req->h_tbl->tbl[TFW_HTTP_HDR_CONNECTION], "Connection: Keep-Alive"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(3, match_id); set_tfw_str(&test_req->h_tbl->tbl[TFW_HTTP_HDR_CONNECTION], "Connection: cLoSe"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(2, match_id); } @@ -217,31 +269,30 @@ TEST(http_match, hdr_host_prefix) TFW_STR2(hdr3, "Host: ", "www"); TFW_STR2(hdr4, "Host: ", "WWW.EXAMPLE.COM:8081"); - test_mlst_add(1, TFW_HTTP_MATCH_F_HDR_CONN, TFW_HTTP_MATCH_O_EQ, - "Connection: Keep-Alive"); - test_mlst_add(2, TFW_HTTP_MATCH_F_HDR_HOST, TFW_HTTP_MATCH_O_PREFIX, - "ex"); - test_mlst_add(3, TFW_HTTP_MATCH_F_HDR_HOST, TFW_HTTP_MATCH_O_PREFIX, - "www.example.com"); + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_HDR_CONN, + "Connection: Keep-Alive"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_HDR_HOST, "ex*"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_HDR_HOST, + "www.example.com*"); set_tfw_str(&test_req->host, "example.com"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr1; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(2, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr2; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); /* Host header contains the header name. */ test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr3; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr4; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(3, match_id); } @@ -263,43 +314,39 @@ TEST(http_match, hdr_host_suffix) TFW_STR2(hdr5, "Host: ", "www"); TFW_STR2(hdr6, "Host: ", "TEST.FOLKS.COM"); - test_mlst_add(1, TFW_HTTP_MATCH_F_HDR_CONN, - TFW_HTTP_MATCH_O_EQ, "Connection: Keep-Alive"); - test_mlst_add(2, TFW_HTTP_MATCH_F_HDR_HOST, - TFW_HTTP_MATCH_O_SUFFIX, ".ru"); - test_mlst_add(3, TFW_HTTP_MATCH_F_HDR_HOST, - TFW_HTTP_MATCH_O_SUFFIX, ".biz"); - test_mlst_add(4, TFW_HTTP_MATCH_F_HDR_HOST, - TFW_HTTP_MATCH_O_SUFFIX, ".folks.com"); - test_mlst_add(5, TFW_HTTP_MATCH_F_HDR_HOST, - TFW_HTTP_MATCH_O_SUFFIX, ".com"); + test_chain_add_rule_str(1, TFW_HTTP_MATCH_F_HDR_CONN, + "Connection: Keep-Alive"); + test_chain_add_rule_str(2, TFW_HTTP_MATCH_F_HDR_HOST, "*.ru"); + test_chain_add_rule_str(3, TFW_HTTP_MATCH_F_HDR_HOST, "*.biz"); + test_chain_add_rule_str(4, TFW_HTTP_MATCH_F_HDR_HOST, "*.folks.com"); + test_chain_add_rule_str(5, TFW_HTTP_MATCH_F_HDR_HOST, "*.com"); set_tfw_str(&test_req->host, "example.com"); - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr1; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(3, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr2; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(5, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr3; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(2, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr4; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(5, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr5; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); test_req->h_tbl->tbl[TFW_HTTP_HDR_HOST] = *hdr6; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(4, match_id); } @@ -310,34 +357,35 @@ TEST(http_match, method_eq) { int match_id; MatchEntry *e1, *e2; - size_t len = FIELD_SIZEOF(TfwHttpMatchArg, method); - e1 = tfw_http_match_entry_new(test_mlst, MatchEntry, rule, len); + e1 = test_rule_container_new(test_chain, MatchEntry, rule, + TFW_HTTP_MATCH_A_METHOD, 0); e1->test_id = 42, e1->rule.field = TFW_HTTP_MATCH_F_METHOD; e1->rule.op = TFW_HTTP_MATCH_O_EQ; e1->rule.arg.type = TFW_HTTP_MATCH_A_METHOD; - e1->rule.arg.len = len; e1->rule.arg.method = TFW_HTTP_METH_POST; + e1->rule.act.type = TFW_HTTP_MATCH_ACT_CHAIN; - e2 = tfw_http_match_entry_new(test_mlst, MatchEntry, rule, len); + e2 = test_rule_container_new(test_chain, MatchEntry, rule, + TFW_HTTP_MATCH_A_METHOD, 0); e2->test_id = 43, e2->rule.field = TFW_HTTP_MATCH_F_METHOD; e2->rule.op = TFW_HTTP_MATCH_O_EQ; e2->rule.arg.type = TFW_HTTP_MATCH_A_METHOD; - e2->rule.arg.len = len; e2->rule.arg.method = TFW_HTTP_METH_GET; + e2->rule.act.type = TFW_HTTP_MATCH_ACT_CHAIN; test_req->method = TFW_HTTP_METH_HEAD; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(-1, match_id); test_req->method = TFW_HTTP_METH_GET; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(43, match_id); test_req->method = TFW_HTTP_METH_POST; - match_id = test_mlst_match(); + match_id = test_chain_match(); EXPECT_EQ(42, match_id); } diff --git a/tempesta_fw/t/unit/test_sched_http.c b/tempesta_fw/t/unit/test_sched_http.c index b6295fdf1..e01e6d2ae 100644 --- a/tempesta_fw/t/unit/test_sched_http.c +++ b/tempesta_fw/t/unit/test_sched_http.c @@ -79,6 +79,7 @@ parse_cfg(const char *cfg_text) * of http scheduler. */ r = tfw_vhost_cfgstart(); + r |= tfw_sched_http_cfgstart(); r |= tfw_cfg_parse_mods(cfg_text, &mod_list); r |= tfw_vhost_cfgend(); r |= tfw_vhost_start(); @@ -107,6 +108,7 @@ cleanup_cfg(void) static void test_req(char *req_str, TfwSrvConn *expect_conn) { + bool block = false; TfwSrvConn *srv_conn = NULL; TfwHttpReq *req = test_req_alloc(req_str? strlen(req_str): 1); @@ -119,22 +121,17 @@ test_req(char *req_str, TfwSrvConn *expect_conn) tfw_http_parse_req(req, req_str_copy, req_str_len); } - req->vhost = tfw_vhost_match((TfwMsg *)req); - if (req->vhost) + req->vhost = tfw_sched_get_vhost((TfwMsg *)req, &block); + if (req->vhost) { + EXPECT_FALSE(block); srv_conn = tfw_vhost_get_srv_conn((TfwMsg *)req); + } EXPECT_EQ(srv_conn, expect_conn); test_req_free(req); tfw_srv_conn_put(srv_conn); } -TEST(tfw_sched_http, sched_null_grp) -{ - TfwScheduler *sched = tfw_sched_lookup("http"); - - EXPECT_TRUE(sched->sched_vhost(NULL) == NULL); -} - TEST(tfw_sched_http, one_wildcard_rule) { TfwSrvGroup *sg; @@ -147,7 +144,7 @@ TEST(tfw_sched_http, one_wildcard_rule) test_start_sg(sg, "ratio", TFW_SG_F_SCHED_RATIO_STATIC); if (parse_cfg("vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default * * *;\n}\n")) { + http_chain {\n -> default;\n}\n")) { TEST_FAIL("can't parse rules\n"); } @@ -227,16 +224,16 @@ TEST(tfw_sched_http, some_rules) vhost vh8 {\nproxy_pass sg8;\n}\n\ vhost vh9 {\nproxy_pass sg9;\n}\n\ vhost vh10 {\nproxy_pass sg10;\n}\n\ - sched_http_rules {\nmatch vh1 uri eq /foo;\n\ - match vh2 uri prefix /foo/bar;\n\ - match vh3 host eq natsys-lab.com;\n\ - match vh4 host prefix natsys-lab;\n\ - match vh5 hdr_host eq google.com;\n\ - match vh6 hdr_host prefix google;\n\ - match vh7 hdr_conn eq close;\n\ - match vh8 hdr_conn prefix Keep;\n\ - match vh9 hdr_raw eq User-Agent:Bot;\n\ - match vh10 hdr_raw prefix X-Forwarded-For;\n}\n")) { + http_chain {\nuri == /foo -> vh1;\n\ + uri == /foo/bar* -> vh2;\n\ + host == natsys-lab.com -> vh3;\n\ + host == natsys-lab* -> vh4;\n\ + hdr_host == google.com -> vh5;\n\ + hdr_host == google* -> vh6;\n\ + hdr_conn == close -> vh7;\n\ + hdr_conn == Keep* -> vh8;\n\ + hdr_raw == User-Agent:Bot -> vh9;\n\ + hdr_raw == X-Forwarded-For* -> vh10;\n}\n")) { TEST_FAIL("can't parse rules\n"); } @@ -275,61 +272,61 @@ typedef struct { TestCase test_cases[] = { { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default uri eq /foo;\n}\n", + http_chain {\nuri == /foo -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo2 HTTP/1.1\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default uri prefix /foo;\n}\n", + http_chain {\nuri == /foo* -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo2 HTTP/1.1\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/bar HTTP/1.1\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default host eq natsys-lab.com;\n}\n", + http_chain {\nhost == natsys-lab.com -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\n\r\n", .bad_req_str = "GET http://natsys-lab2.com/foo HTTP/1.1\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default host prefix natsys-lab;\n}\n", + http_chain {\nhost == natsys-lab* -> default;\n}\n", .good_req_str = "GET http://natsys-lab2.com/foo HTTP/1.1\r\n\r\n", .bad_req_str = "GET http://google.com/foo HTTP/1.1\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default hdr_host eq natsys-lab.com;\n}\n", + http_chain {\nhdr_host == natsys-lab.com -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nHost: natsys-lab.com\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nHost: natsys-lab2.com\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default hdr_host prefix natsys-lab;\n}\n", + http_chain {\nhdr_host == natsys-lab* -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nHost: natsys-lab2.com\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nHost: google.com\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default hdr_conn eq Keep-Alive;\n}\n", + http_chain {\nhdr_conn == Keep-Alive -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nConnection: close\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default hdr_conn prefix Keep;\n}\n", + http_chain {\nhdr_conn == Keep* -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nConnection: Keep-Alive\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nConnection: close\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default hdr_raw eq User-Agent:Bot;\n}\n", + http_chain {\nhdr_raw == User-Agent:Bot -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nUser-Agent:Bot\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nUser-Agent:Tot\r\n\r\n", }, { .rule_str = "vhost default {\nproxy_pass default;\n}\n\ - sched_http_rules {\nmatch default hdr_raw prefix User-Agent;\n}\n", + http_chain {\nhdr_raw == User-Agent* -> default;\n}\n", .good_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nUser-Agent: Bot\r\n\r\n", .bad_req_str = "GET http://natsys-lab.com/foo HTTP/1.1\r\nConnection: close\r\n\r\n", }, @@ -380,7 +377,6 @@ TEST_SUITE(sched_http) kernel_fpu_begin(); - TEST_RUN(tfw_sched_http, sched_null_grp); TEST_RUN(tfw_sched_http, one_wildcard_rule); TEST_RUN(tfw_sched_http, some_rules); TEST_RUN(tfw_sched_http, one_rule); diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index 6fd3baa1d..bbf494d29 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -254,17 +254,6 @@ tfw_location_match(TfwVhost *vhost, TfwStr *arg) return NULL; } -TfwVhost * -tfw_vhost_match(TfwMsg *msg) -{ - TfwVhost *vhost; - - if ((vhost = tfw_sched_get_vhost(msg))) - tfw_vhost_get(vhost); - - return vhost; -} - /* * Find request's location with server group linked. If there is no * separate location for request (or there is no server group linked diff --git a/tempesta_fw/vhost.h b/tempesta_fw/vhost.h index 0b8016f41..cfe51066a 100644 --- a/tempesta_fw/vhost.h +++ b/tempesta_fw/vhost.h @@ -197,7 +197,6 @@ TfwNipDef *tfw_nipdef_match(TfwLocation *loc, unsigned char meth, TfwStr *arg); bool tfw_capuacl_match(TfwAddr *addr); TfwCaPolicy *tfw_capolicy_match(TfwLocation *loc, TfwStr *arg); TfwLocation *tfw_location_match(TfwVhost *vhost, TfwStr *arg); -TfwVhost *tfw_vhost_match(TfwMsg *msg); TfwVhost *tfw_vhost_lookup(const char *name); TfwSrvConn *tfw_vhost_get_srv_conn(TfwMsg *msg); TfwVhost *tfw_vhost_new(const char *name);