From a062b86eeaae73655773ace8037d332d3c8a77e3 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 8 Oct 2014 10:18:18 +0400 Subject: [PATCH] migrate tfw_sched_match to the new http_match API related to: #5 - Load balancing --- tempesta.sh | 4 +- tempesta_fw/http_match.c | 58 +- tempesta_fw/http_match.h | 33 +- tempesta_fw/sched/Makefile | 5 +- tempesta_fw/sched/dummy/Makefile | 21 - tempesta_fw/sched/match/Makefile | 22 - tempesta_fw/sched/match/sched_match.c | 606 ------------- tempesta_fw/sched/match/sched_match.h | 68 -- tempesta_fw/sched/match/sysctl.c | 422 --------- tempesta_fw/sched/rr/Makefile | 21 - .../sched/{dummy => }/tfw_sched_dummy.c | 0 tempesta_fw/sched/tfw_sched_http.c | 842 ++++++++++++++++++ tempesta_fw/sched/{rr => }/tfw_sched_rr.c | 0 tempesta_fw/t/test_http_match.c | 11 +- 14 files changed, 906 insertions(+), 1207 deletions(-) delete mode 100644 tempesta_fw/sched/dummy/Makefile delete mode 100644 tempesta_fw/sched/match/Makefile delete mode 100644 tempesta_fw/sched/match/sched_match.c delete mode 100644 tempesta_fw/sched/match/sched_match.h delete mode 100644 tempesta_fw/sched/match/sysctl.c delete mode 100644 tempesta_fw/sched/rr/Makefile rename tempesta_fw/sched/{dummy => }/tfw_sched_dummy.c (100%) create mode 100644 tempesta_fw/sched/tfw_sched_http.c rename tempesta_fw/sched/{rr => }/tfw_sched_rr.c (100%) diff --git a/tempesta.sh b/tempesta.sh index aa31b54386..c5672a567c 100755 --- a/tempesta.sh +++ b/tempesta.sh @@ -7,7 +7,7 @@ SSOCKET=sync_socket TDB=tempesta_db TFW=tempesta_fw -SCHED=dummy +SCHED=http TFW_ROOT=`pwd`/$TFW TFW_CACHE_SIZE=`expr 256 \* 1024` TFW_CACHE_PATH=$TFW_ROOT/cache @@ -45,7 +45,7 @@ start() cache_path="$TFW_CACHE_PATH" [ $? -ne 0 ] && error "cannot load tempesta module" - insmod $TFW_ROOT/sched/$SCHED/*.ko + insmod $TFW_ROOT/sched/tfw_sched_${SCHED}.ko [ $? -ne 0 ] && error "cannot load scheduler module" sysctl --load=tempesta.sysctl.conf diff --git a/tempesta_fw/http_match.c b/tempesta_fw/http_match.c index 932d8c0c56..7d348acea5 100644 --- a/tempesta_fw/http_match.c +++ b/tempesta_fw/http_match.c @@ -37,13 +37,13 @@ match_uri_eq(const TfwHttpReq *req, const TfwMatchArg *arg) * - Characters other than those in the "reserved" and "unsafe" * sets (see RFC 2396) are equivalent to their ""%" HEX HEX" encoding. */ - return tfw_str_eq_cstr_ci(&req->uri, arg->str.s, arg->str.len); + return tfw_str_eq_cstr_ci(&req->uri, arg->str, arg->len); } static bool match_uri_prefix(const TfwHttpReq *req, const TfwMatchArg *arg) { - return tfw_str_startswith_cstr_ci(&req->uri, arg->str.s, arg->str.len); + return tfw_str_startswith_cstr_ci(&req->uri, arg->str, arg->len); } static bool @@ -54,7 +54,7 @@ match_host_eq(const TfwHttpReq *req, const TfwMatchArg *arg) * URI or Host header (the header is used if URI is not absolute). * - Empty port and default port (80) are equal when comparing URIs. */ - return tfw_str_eq_cstr_ci(&req->host, arg->str.s, arg->str.len); + return tfw_str_eq_cstr_ci(&req->host, arg->str, arg->len); } static bool @@ -67,7 +67,7 @@ match_headers(const TfwHttpReq *req, const TfwMatchArg *arg, for (i = 0; i < tbl->size; ++i) { hdr = &tbl->tbl[i].field; - if (cmp_fn(hdr, arg->str.s, arg->str.len)) + if (cmp_fn(hdr, arg->str, arg->len)) return true; } @@ -144,6 +144,28 @@ tfw_http_match_req(const TfwHttpReq *req, const TfwHttpMatchList *mlst) } EXPORT_SYMBOL(tfw_http_match_req); +TfwHttpMatchRule * +tfw_http_match_rule_new(TfwHttpMatchList *mlst, size_t arg_len) +{ + TfwHttpMatchRule *rule; + size_t size = TFW_HTTP_MATCH_RULE_SIZE(arg_len); + + BUG_ON(!mlst || !mlst->pool); + + rule = tfw_pool_alloc(mlst->pool, size); + if (!rule) { + TFW_ERR("Can't allocate a rule for match list: %p\n", mlst); + return NULL; + } + + memset(rule, 0, size); + INIT_LIST_HEAD(&rule->list); + list_add_tail(&rule->list, &mlst->list); + + return rule; +} +EXPORT_SYMBOL(tfw_http_match_rule_new); + TfwHttpMatchList * tfw_http_match_list_alloc(void) { @@ -164,29 +186,15 @@ EXPORT_SYMBOL(tfw_http_match_list_alloc); void tfw_http_match_list_free(TfwHttpMatchList *mlst) { - BUG_ON(!mlst || !mlst->pool); - tfw_pool_free(mlst->pool); + if (mlst) + tfw_pool_free(mlst->pool); } EXPORT_SYMBOL(tfw_http_match_list_free); -TfwHttpMatchRule * -tfw_http_match_rule_new(TfwHttpMatchList *mlst, size_t extra_len) +void +tfw_http_match_list_rcu_free(struct rcu_head *r) { - TfwHttpMatchRule *rule; - size_t size = sizeof(*rule) + extra_len; - - BUG_ON(!mlst || !mlst->pool); - - rule = tfw_pool_alloc(mlst->pool, size); - if (!rule) { - TFW_ERR("Can't allocate a rule for match list: %p\n", mlst); - return NULL; - } - - memset(rule, 0, size); - INIT_LIST_HEAD(&rule->list); - list_add_tail(&rule->list, &mlst->list); - - return rule; + TfwHttpMatchList *l = container_of(r, TfwHttpMatchList, rcu); + tfw_pool_free(l->pool); } -EXPORT_SYMBOL(tfw_http_match_rule_new); +EXPORT_SYMBOL(tfw_http_match_list_rcu_free); diff --git a/tempesta_fw/http_match.h b/tempesta_fw/http_match.h index ae5927b4b3..d433dabff1 100644 --- a/tempesta_fw/http_match.h +++ b/tempesta_fw/http_match.h @@ -21,6 +21,7 @@ #define __TFW_HTTP_MATCH_H__ #include +#include #include "pool.h" #include "http.h" @@ -42,14 +43,12 @@ typedef enum { } tfw_http_match_op_t; typedef struct { - short len; - const char *s; -} TfwMatchArgStr; - -typedef union { - unsigned char method; - TfwAddr addr; - TfwMatchArgStr str; + short len; /* Actual amount of memory allocated for the union below. */ + union { + unsigned char method; + TfwAddr addr; + char str[0]; + }; } TfwMatchArg; typedef struct { @@ -57,19 +56,27 @@ typedef struct { tfw_http_match_fld_t field; tfw_http_match_op_t op; TfwMatchArg arg; - char extra[0]; /* May be consumed for variable-length @arg. */ } TfwHttpMatchRule; +#define TFW_HTTP_MATCH_RULE_SIZE(arg_len) \ + (offsetof(TfwHttpMatchRule, arg.str) + arg_len) + +#define TFW_HTTP_MATCH_CONT_SIZE(container_struct_name, arg_len) \ + (sizeof(container_struct_name) - sizeof(TfwHttpMatchRule) \ + + TFW_HTTP_MATCH_RULE_SIZE(arg_len)) + typedef struct { struct list_head list; TfwPool *pool; + struct rcu_head rcu; } TfwHttpMatchList; TfwHttpMatchList *tfw_http_match_list_alloc(void); void tfw_http_match_list_free(TfwHttpMatchList *); -TfwHttpMatchRule *tfw_http_match_rule_new(TfwHttpMatchList *, size_t extra_len); -TfwHttpMatchRule * tfw_http_match_req(const TfwHttpReq *, const TfwHttpMatchList *); +void tfw_http_match_list_rcu_free(struct rcu_head *); +TfwHttpMatchRule *tfw_http_match_req(const TfwHttpReq *, const TfwHttpMatchList *); +TfwHttpMatchRule *tfw_http_match_rule_new(TfwHttpMatchList *, size_t arg_len); #define tfw_http_match_req_entry(req, mlst, container, member) \ ({ \ @@ -80,9 +87,9 @@ TfwHttpMatchRule * tfw_http_match_req(const TfwHttpReq *, const TfwHttpMatchList _c; \ }) -#define tfw_http_match_entry_new(mlst, container, member, extra_len) \ +#define tfw_http_match_entry_new(mlst, container, member, arg_len) \ ({ \ - size_t _s = sizeof(container) + (extra_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"); \ diff --git a/tempesta_fw/sched/Makefile b/tempesta_fw/sched/Makefile index 9fe451b9d0..ef47ab43ee 100644 --- a/tempesta_fw/sched/Makefile +++ b/tempesta_fw/sched/Makefile @@ -16,5 +16,8 @@ # this program; if not, write to the Free Software Foundation, Inc., 59 # Temple Place - Suite 330, Boston, MA 02111-1307, USA. -obj-m = dummy/ rr/ match/ +EXTRA_CFLAGS = -I$(src)/../ -I$(src)/../../tempesta_db -I$(src)/../../sync_socket +EXTRA_CFLAGS += -Og -g3 -DDEBUG + +obj-m = tfw_sched_dummy.o tfw_sched_rr.o tfw_sched_http.o \ No newline at end of file diff --git a/tempesta_fw/sched/dummy/Makefile b/tempesta_fw/sched/dummy/Makefile deleted file mode 100644 index 4133544a21..0000000000 --- a/tempesta_fw/sched/dummy/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# Tempesta FW -# -# Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, -# or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 59 -# Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -EXTRA_CFLAGS = -DDEBUG -I$(src)/../../ -I$(src)/../../../tempesta_db -I$(src)/../../../sync_socket - -obj-m = tfw_sched_dummy.o diff --git a/tempesta_fw/sched/match/Makefile b/tempesta_fw/sched/match/Makefile deleted file mode 100644 index ec683f1233..0000000000 --- a/tempesta_fw/sched/match/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# Tempesta FW -# -# Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, -# or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 59 -# Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -EXTRA_CFLAGS = -DDEBUG -I$(src)/../../ -I$(src)/../../../tempesta_db -I$(src)/../../../sync_socket - -obj-m = tfw_sched_match.o -tfw_sched_match-objs += sysctl.o sched_match.o diff --git a/tempesta_fw/sched/match/sched_match.c b/tempesta_fw/sched/match/sched_match.c deleted file mode 100644 index f0d3d674fa..0000000000 --- a/tempesta_fw/sched/match/sched_match.c +++ /dev/null @@ -1,606 +0,0 @@ -/** - * Tempesta FW - * - * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, - * or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include - -#include "http.h" -#include "sched.h" -#include "server.h" - -#include "sched_match.h" - -MODULE_AUTHOR(TFW_AUTHOR); -MODULE_DESCRIPTION("Tempesta request-matching scheduler"); -MODULE_VERSION("0.0.1"); -MODULE_LICENSE("GPL"); - - -/* How does the scheduler work: - * - * There is a global matching table that contains rules of the following format: - * { subject, operator, pattern, servers_list } - * For example: - * { SUBJ_HOST, OP_EQUAL, "example.com", { TfwServer, TfwServer, TfwServer } } - * { SUBJ_URI, OP_PREFIX, "/foo/bar", { TfwServer } } - * { SUBJ_URI, OP_PREFIX, "/", { TfwServer, TfwServer } } - * - * The 'subject' determines a field of a HTTP request (uri, host, header, etc). - * The 'operator' determines how to compare the field with the 'pattern'. - * Each incoming HTTP request is sequentially matched against each entry in the - * table until. If a matching entry is found, then the algorithm stops and - * returns a server from the list. - * - * Also there are two additional entities: - * - A list of all online servers added to the scheduler. - * - A table of rules that contains addresses instead of TfwServer objects. - * When either one of them is changed, both are merged into a single matching - * table that combines matching rules and TfwServer objects. - */ - -typedef struct { - u16 counter; - u8 srv_max; - u8 srv_n; - TfwServer *srv[0]; -} SrvList; - -#define SIZE_OF_SRV_LIST(n) (sizeof(SrvList) + sizeof(TfwServer) * (n)) - -typedef struct { - subj_t subj; - op_t op; - short len; - const char *pattern; - SrvList *servers; -} MatchTblEntry; - -typedef struct { - int count; - MatchTblEntry *entries[RULE_MAX_COUNT]; - TfwPool *pool; -} MatchTbl; - - -/** - * The matching table. - * Generally it is read-only, once it is generated it is not updated. - * When a change occurs, a new table is generated and this pointer is replaced - * via the RCU mechanism. - */ -static MatchTbl *match_tbl = NULL; - -/** - * List of all known servers. - * Allocated once upon initialization, then updated in-place by - * tfw_sched_match_add_srv() and tfw_sched_match_del_srv(). - */ -static SrvList *added_servers = NULL; - -/** - * Parsed rules. - * The differences from the MatchTbl are: - * - Contains addresses instead of TfwServer objects. - * - Not read-only, the contents is updated when rules are changed. - * - Allocated statically, the whole table is overwritten by apply_new_rules(). - */ -static RuleTbl rule_tbl; - -/** - * The lock for serializing writers. - * Must be held when either added_servers or rule_tbl is accessed. - */ -DEFINE_SPINLOCK(sched_match_update_lock); - - -/* - * -------------------------------------------------------------------------- - * Supplementary functions. - * -------------------------------------------------------------------------- - */ - -static void -dbg_print_entry(const char *msg, const MatchTblEntry *e, bool print_servers) -{ - DBG("%s: subj=%d op=%d pattern='%.*s'\n", - msg, e->subj, e->op, e->len, e->pattern); - - if (print_servers) { - int i; - char buf[32]; - const SrvList *s = e->servers; - - DBG("Servers (total %d):\n", s->srv_n); - - for (i = 0; i < s->srv_n; ++i) { - tfw_server_snprint(s->srv[i], buf, sizeof(buf)); - DBG(" #%d - %s\n", i, buf); - } - } -} - -static int -srv_list_find(const SrvList *list, const TfwServer *srv) -{ - int i; - - BUG_ON(!list || !srv); - - for (i = 0; i < list->srv_n; ++i) { - if (list->srv[i] == srv) - return i; - } - - return -1; -} - -static int -srv_list_add(SrvList *list, TfwServer *srv) -{ - BUG_ON(!list || !srv); - - if (srv_list_find(list, srv) > 0) { - ERR("The server is already present in the list\n"); - return -EEXIST; - } - else if (list->srv_n >= list->srv_max) { - ERR("No space left in the servers list\n"); - return -ENOMEM; - } - - list->srv[list->srv_n] = srv; - ++list->srv_n; - - return 0; -} - -static int -srv_list_del(SrvList *list, TfwServer *srv) -{ - int idx; - - BUG_ON(!list || !srv); - - idx = srv_list_find(list, srv); - - if (idx < 0) { - ERR("Server is not found in the list\n"); - return -ENOENT; - } - - list->srv[idx] = list->srv[list->srv_n - 1]; - list->srv[list->srv_n] = NULL; - --list->srv_n; - - return 0; -} - -static TfwServer * -srv_list_get_rr(SrvList *list) -{ - unsigned int n; - TfwServer *srv; - - BUG_ON(!list); - - do { - n = list->srv_n; - if (!n) { - ERR("The servers list is empty\n"); - return NULL; - } - - srv = list->srv[list->counter++ % n]; - } while (!srv); - - return srv; -} - -static TfwServer * -srv_list_get_by_addr(const SrvList *list, const TfwAddr *addr) -{ - int i; - int ret; - TfwServer *curr_srv; - TfwAddr curr_addr; - - BUG_ON(!list || !addr); - - for (i = 0; i < list->srv_n; ++i) { - curr_srv = list->srv[i]; - - ret = tfw_server_get_addr(curr_srv, &curr_addr); - if (ret) { - ERR("Can't obtain address of the server: %p\n", curr_srv); - return NULL; - } - - if (tfw_addr_eq(addr, &curr_addr)) { - return curr_srv; - } - } - - return NULL; -} - -/* - * -------------------------------------------------------------------------- - * MatchTbl related functions. - * -------------------------------------------------------------------------- - */ -/** - * Merge a table of rules and a list of servers into a single matching table. - * - * The function copies elements from @rule_tbl into @tbl and resolves IP - * addresses to TfwServer objects from the given @all_servers list. - * - * @rule_tbl Contains a set of rules and corresponding IP addresses. - * @all_servers Contains a list of all known servers. - * - * @tbl The output table to be filled. - * Not allocated automatically, upon the call it must be pre-allocated - * using tfw_pool_new(). - * - * Also, the function uses @tbl->pool to allocate new elements, so you are - * responsible for freeing them, even if the function returns an error. - */ -static int -fill_match_tbl(const RuleTbl *rule_tbl, const SrvList *all_servers, MatchTbl *tbl) -{ - int rule_idx, addr_idx, pattern_len; - char *pattern; - const Rule *rule; - MatchTblEntry *entry; - SrvList *servers; - - for (rule_idx = 0; rule_idx < rule_tbl->rules_n; ++rule_idx) { - rule = &rule_tbl->rules[rule_idx]; - pattern_len = strnlen(rule->pattern, sizeof(rule->pattern)); - - /* Allocate memory. */ - entry = tfw_pool_alloc(tbl->pool, sizeof(*entry)); - pattern = tfw_pool_alloc(tbl->pool, pattern_len); - servers = tfw_pool_alloc(tbl->pool, SIZE_OF_SRV_LIST(rule->addrs_n)); - if (!entry || !pattern || !servers) { - ERR("Can't allocate memory\n"); - return -1; - } - - /* Resolve IP addresses to TfwServer pointers. */ - servers->srv_max = rule->addrs_n; - for (addr_idx = 0; addr_idx < rule->addrs_n; ++addr_idx) { - const TfwAddr *addr = &rule->addrs[addr_idx]; - TfwServer *srv = srv_list_get_by_addr(all_servers, addr); - if (srv) { - srv_list_add(servers, srv); - } else { - TFW_ERR_ADDR("No server found for addr", addr); - } - } - - /* Copy data from Rule to the newly created MatchTblEntry. */ - memcpy(pattern, rule->pattern, pattern_len); - entry->subj = rule->subj; - entry->op = rule->op; - entry->pattern = pattern; - entry->len = pattern_len; - entry->servers = servers; - - tbl->entries[tbl->count++] = entry; - } - - return 0; -} - -/** - * The function does two things: - * 1. Builds new MatchTbl from a set of Rules (stored in 'rule_tbl') and a set - * of online servers (stored in 'added_servers'). - * 2. Replace the global 'match_tbl' with a fresh one using the RCU mechanism. - * - * It should be called when rules or servers are changed. - */ -static int -refresh_match_tbl(void) -{ - int ret; - MatchTbl *old_tbl, *new_tbl; - - new_tbl = tfw_pool_new(MatchTbl, TFW_POOL_ZERO); - if (!new_tbl) { - ERR("Can't create a new matching tabe\n"); - return -1; - } - - spin_lock_bh(&sched_match_update_lock); - ret = fill_match_tbl(&rule_tbl, added_servers, new_tbl); - spin_unlock_bh(&sched_match_update_lock); - - if (ret) { - ERR("Can't fill a new matching table\n"); - tfw_pool_free(new_tbl->pool); - return ret; - } - - DBG("Replacing the matching table\n"); - - old_tbl = match_tbl; - rcu_assign_pointer(match_tbl, new_tbl); - synchronize_rcu(); - - if (old_tbl) { - DBG("Freeing old matching table\n"); - tfw_pool_free(old_tbl->pool); - } - - return 0; -} - -/* - * -------------------------------------------------------------------------- - * Functions for matching HTTP requests against MatchTbl. - * -------------------------------------------------------------------------- - */ - -/** - * Evaluate an "expression" of a form: (subject [operator] pattern). - * - * The function maps a given @op to a C function that compares strings, - * passes arguments @str and @cstr to it and returns the result of comparison. - */ -static bool -apply_str_op(op_t op, const TfwStr *str, const char *cstr, int cstr_len) -{ - - static const typeof(&tfw_str_eq_cstr_ci) fns[] = { - [OP_EQUAL] = tfw_str_eq_cstr_ci, - [OP_PREFIX] = tfw_str_startswith_cstr_ci, - }; - - BUG_ON(op >= ARRAY_SIZE(fns)); - BUG_ON(!fns[op]); - - return fns[op](str, cstr, cstr_len); -} - -static bool -match_uri(const TfwHttpReq *r, const MatchTblEntry *e) -{ - return apply_str_op(e->op, &r->uri, e->pattern, e->len); -} - -static bool -match_host(const TfwHttpReq *r, const MatchTblEntry *e) -{ - const TfwStr *host = &r->host; - - if (!host) - host = &r->h_tbl->tbl[TFW_HTTP_HDR_HOST].field; - - return apply_str_op(e->op, host, e->pattern, e->len); -} - -static bool -match_any_header(const TfwHttpReq *r, const MatchTblEntry *e) -{ - int i; - TfwStr *hdr; - TfwHttpHdrTbl *tbl = r->h_tbl; - - for (i = 0; i < tbl->size; ++i) { - hdr = &tbl->tbl[i].field; - if (apply_str_op(e->op, hdr, e->pattern, e->len)) - return true; - - } - - return false; -} - -static bool -match(const TfwHttpReq *req, const MatchTblEntry *entry) -{ - static const typeof(&match_uri) fns[] = { - [SUBJ_HOST] = match_host, - [SUBJ_URI] = match_uri, - [SUBJ_HEADER] = match_any_header, - }; - subj_t subj = entry->subj; - - BUG_ON(subj >= ARRAY_SIZE(fns)); - BUG_ON(!fns[subj]); - - return fns[subj](req, entry); -} - -static TfwServer * -do_matches(const TfwHttpReq *req, const MatchTbl *tbl) -{ - int i; - MatchTblEntry *entry; - TfwServer *srv; - - DBG("Matching request: %p against %d rules\n", req, tbl->count); - - for (i = 0; i < tbl->count; ++i) { - entry = tbl->entries[i]; - - if (match(req, entry)) { - dbg_print_entry("Match", entry, true); - - srv = srv_list_get_rr(entry->servers); - if (srv) - return srv; - } else { - dbg_print_entry("No match", entry, false); - } - } - - return NULL; -} - -/* - * -------------------------------------------------------------------------- - * Scheduler API - * -------------------------------------------------------------------------- - */ - -TfwServer * -tfw_sched_match_get_srv(TfwMsg *msg) -{ - MatchTbl *tbl = NULL; - TfwServer *srv = NULL; - - if (!added_servers->srv_n) { - ERR("The scheduler's server list is empty\n"); - return NULL; - } - - rcu_read_lock(); - tbl = rcu_dereference(match_tbl); - if (!tbl) { - ERR("The scheduler's matchig table is empty\n"); - } else { - srv = do_matches((TfwHttpReq *)msg, tbl); - } - rcu_read_unlock(); - - if (!srv) - ERR("A matching server is not found\n"); - - return srv; -} - -int -tfw_sched_match_add_srv(TfwServer *srv) -{ - int ret = 0; - - DBG("Adding server: %p\n", srv); - - spin_lock_bh(&sched_match_update_lock); - ret = srv_list_add(added_servers, srv); - spin_unlock_bh(&sched_match_update_lock); - - if (ret) { - ERR("Can't add the server to the scheduler: %p\n", srv); - return -1; - } - - ret = refresh_match_tbl(); - if (ret) { - ERR("Can't re-build the matching table\n"); - } - - return ret; -} - -int -tfw_sched_match_del_srv(TfwServer *srv) -{ - int ret = 0; - - DBG("Deleting server: %p\n", srv); - - spin_lock_bh(&sched_match_update_lock); - ret = srv_list_del(added_servers, srv); - spin_unlock_bh(&sched_match_update_lock); - - if (ret) { - ERR("Can't delete the server from the scheduler: %p\n", srv); - return -1; - } - - ret = refresh_match_tbl(); - if (ret) { - ERR("Can't refresh the matching table\n"); - } - - return ret; -} - -int -apply_new_rules(const RuleTbl *tbl) -{ - int ret; - - DBG("Applying new matching rules\n"); - - spin_lock_bh(&sched_match_update_lock); - memcpy(&rule_tbl, tbl, sizeof(rule_tbl)); - spin_unlock_bh(&sched_match_update_lock); - - ret = refresh_match_tbl(); - if (ret) { - ERR("Can't refresh the matching table\n"); - } - - return ret; -} - -extern int sysctl_register(void); -extern void sysctl_unregister(void); - -int -tfw_sched_match_init(void) -{ - int ret; - - static TfwScheduler tfw_sched_rr_mod = { - .name = "match", - .get_srv = tfw_sched_match_get_srv, - .add_srv = tfw_sched_match_add_srv, - .del_srv = tfw_sched_match_del_srv - }; - - LOG("init\n"); - - added_servers = kzalloc(SIZE_OF_SRV_LIST(TFW_SCHED_MAX_SERVERS), GFP_KERNEL); - if (!added_servers) { - ERR("Can't allocate servers list\n"); - return -ENOMEM; - } - added_servers->srv_max = TFW_SCHED_MAX_SERVERS; - - - ret = sysctl_register(); - if (ret) { - ERR("Can't register the sysctl table\n"); - return ret; - } - - ret = tfw_sched_register(&tfw_sched_rr_mod); - if (ret) { - ERR("Can't register the scheduler module\n"); - } - - return ret; -} -module_init(tfw_sched_match_init); - -void -tfw_sched_match_exit(void) -{ - tfw_sched_unregister(); - sysctl_unregister(); - kfree(added_servers); -} -module_exit(tfw_sched_match_exit); - - diff --git a/tempesta_fw/sched/match/sched_match.h b/tempesta_fw/sched/match/sched_match.h deleted file mode 100644 index b6273647c7..0000000000 --- a/tempesta_fw/sched/match/sched_match.h +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Tempesta FW - * - * Internal scheduler declarations. - * - * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, - * or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ -#ifndef __TFW_SCHED_MATCH_H__ -#define __TFW_SCHED_MATCH_H__ - -#include "tempesta.h" -#include "addr.h" - -#define LOG_BANNER "tfw_sched_match: " -#define ERR(...) TFW_ERR(LOG_BANNER __VA_ARGS__) -#define LOG(...) TFW_LOG(LOG_BANNER __VA_ARGS__) -#define DBG(...) TFW_DBG(LOG_BANNER __VA_ARGS__) - -#define RULE_MAX_COUNT 64 -#define RULE_PATTERN_SIZE 256 -#define RULE_ADDR_COUNT 16 - -typedef enum { - SUBJ_NA = 0, - SUBJ_HOST, - SUBJ_URI, - SUBJ_HEADER -} subj_t; - -typedef enum { - OP_NA = 0, - OP_EQUAL, - OP_PREFIX, -} op_t; - -typedef struct { - subj_t subj; - op_t op; - size_t addrs_n; - char pattern[RULE_PATTERN_SIZE]; - TfwAddr addrs[RULE_ADDR_COUNT]; -} Rule; - -typedef struct { - size_t rules_n; - Rule rules[RULE_MAX_COUNT]; -} RuleTbl; - -int apply_new_rules(const RuleTbl *tbl); - -op_t op_from_str(const char *str, size_t maxlen); -subj_t subj_from_str(const char *str, size_t maxlen); - -#endif /* __TFW_SCHED_MATCH_H__ */ diff --git a/tempesta_fw/sched/match/sysctl.c b/tempesta_fw/sched/match/sysctl.c deleted file mode 100644 index 76b786ac5e..0000000000 --- a/tempesta_fw/sched/match/sysctl.c +++ /dev/null @@ -1,422 +0,0 @@ -/** - * Tempesta FW - * - * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, - * or (at your option) any later version. - * - * This program 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., 59 - * Temple Place - Suite 330, Boston, MA 02111-1307, USA. - */ - -#include -#include -#include -#include - -#include "lib.h" -#include "log.h" -#include "sched_match.h" - -#define RULES_TEXT_BUF_SIZE 1024 -#define IP_ADDR_TEXT_BUF_SIZE 64 - -/* - * The following code is a sysctl interface with a primitive descend parser - * for the configuration. The code is a bit awkward, it is a subject to change - * in the near future. - */ - -typedef enum { - TOKEN_NA = 0, - TOKEN_LBRACE, - TOKEN_RBRACE, - TOKEN_STR, -} token_t; - -static const char *token_str_tbl[] = { - [TOKEN_NA] = STRINGIFY(TOKEN_NA), - [TOKEN_LBRACE] = STRINGIFY(TOKEN_LBRACE), - [TOKEN_RBRACE] = STRINGIFY(TOKEN_RBRACE), - [TOKEN_STR] = STRINGIFY(TOKEN_STR), -}; - -const char * -token_str(token_t t) -{ - BUG_ON(t >= ARRAY_SIZE(token_str_tbl)); - return token_str_tbl[t]; -} - -typedef struct { - token_t token; - int len; - const char *lexeme; - const char *pos; - Rule *rule; - RuleTbl *tbl; -} ParserState; - -#define PARSER_ERR(s, ...) \ -do { \ - ERR("Parser error: " __VA_ARGS__); \ - ERR("lexeme: %.*s position: %.80s\n", s->len, s->lexeme, s->pos); \ -} while (0) - -token_t get_token(ParserState *s) -{ - static const token_t single_char_tokens[] = { - [0 ... 255] = TOKEN_NA, - ['{'] = TOKEN_LBRACE, - ['}'] = TOKEN_RBRACE, - }; - const char *p; - - s->token = TOKEN_NA; - s->lexeme = NULL; - s->len = 0; - s->pos = skip_spaces(s->pos); - - if (!s->pos[0]) - goto out; - s->lexeme = s->pos; - - s->token = single_char_tokens[(u8)s->pos[0]]; - if (s->token) { - s->pos++; - s->len = 1; - goto out; - } - - if (s->lexeme[0] == '"') { - for (p = s->pos + 1; *p; ++p) { - if (*p == '"' && *(p - 1) != '\\') { - break; - } - } - if (*p == '"') { - s->lexeme++; - s->len = (p - s->lexeme); - s->pos = ++p; - s->token = TOKEN_STR; - } else { - PARSER_ERR(s, "unterminated quote"); - } - } else { - for (p = s->pos + 1; *p && !isspace(*p); ++p); - s->len = (p - s->pos); - s->pos = p; - s->token = TOKEN_STR; - } - -out: - return s->token; -} - -static token_t -peek_token(ParserState *s) -{ - ParserState old_state = *s; - token_t t = get_token(s); - *s = old_state; - - return t; -} - -/* - * The following functions are trying to mimic something like this: - * input ::= rules - * rules ::= rule rules - * rule ::= subj - * | op - * | pattern - * | LBRACE - * | servers - * | RBRACE - * servers ::= server servers - * server ::= STR - * subj ::= STR - * op ::= STR - * pattern ::= STR - */ - -#define EXPECT(token, s, action_if_unexpected) \ -do { \ - token_t _t = peek_token(s); \ - if (_t != token) { \ - PARSER_ERR(s, "Unexpected token: %s (expected %s)\n", \ - token_str(_t), token_str(token)); \ - action_if_unexpected; \ - } \ -} while (0) - -#define EXPECT_EITHER(t1, t2, s, action_if_unexpected) \ -({ \ - token_t _t = peek_token(s); \ - if (_t != t1 && _t != t2) { \ - PARSER_ERR(s, "Unexpected token: %s (expected: %s or %s)\n", \ - token_str(_t) token_str(t1), token_str(t2)); \ - action_if_unexpected; \ - } \ - _t; \ -}) - -#define IDX_BY_STR(array, str, maxlen) \ -({ \ - int _found_idx = 0; \ - int _i; \ - for (_i = 0; _i < ARRAY_SIZE(array); ++_i) { \ - if (!strncmp(str, array[_i], maxlen)) { \ - _found_idx = _i; \ - break; \ - } \ - } \ - _found_idx; \ -}) - -static int -parse_subj(ParserState *s) -{ - static const char *subj_str_tbl[] = { - [SUBJ_NA] = STRINGIFY(SUBJ_NA), - [SUBJ_HOST] = "host", - [SUBJ_URI] = "uri", - [SUBJ_HEADER] = "header", - }; - subj_t subj; - - EXPECT(TOKEN_STR, s, return -1); - get_token(s); - - subj = IDX_BY_STR(subj_str_tbl, s->lexeme, s->len); - if (!subj) { - PARSER_ERR(s, "invalid subject"); - return -1; - } - - s->rule->subj = subj; - - return 0; -} - -static int -parse_op(ParserState *s) -{ - static const char *op_str_tbl[] = { - [OP_NA] = STRINGIFY(OP_NA), - [OP_EQUAL] = "=", - [OP_PREFIX] = "^", - }; - op_t op; - - EXPECT(TOKEN_STR, s, return -1); - get_token(s); - - op = IDX_BY_STR(op_str_tbl, s->lexeme, s->len); - if (!op) { - PARSER_ERR(s, "invalid operator"); - return -1; - } - - s->rule->op = op; - - return 0; -} - -static int -parse_pattern(ParserState *s) -{ - EXPECT(TOKEN_STR, s, return -1); - get_token(s); - - if (s->len >= sizeof(s->rule->pattern)) { - PARSER_ERR(s, "too long pattern: %.*s", s->len, s->lexeme); - return -1; - } - - memcpy(s->rule->pattern, s->lexeme, s->len); - s->rule->pattern[s->len] = '\0'; - - return 0; -} - -static int -parse_server(ParserState *s) -{ - int ret = 0; - TfwAddr *addr; - char *pos; - char buf[IP_ADDR_TEXT_BUF_SIZE + 1]; - - EXPECT(TOKEN_STR, s, return -1); - get_token(s); - - if (s->rule->addrs_n >= ARRAY_SIZE(s->rule->addrs)) { - PARSER_ERR(s, "max number of addresses per rule reached"); - return -1; - } - - if (s->len >= sizeof(buf)) { - PARSER_ERR(s, "too long address: %.*s", s->len, s->lexeme); - return -1; - } - - memcpy(buf, s->lexeme, s->len); - buf[s->len] = '\0'; - pos = buf; - addr = &s->rule->addrs[s->rule->addrs_n++]; - ret = tfw_inet_pton(&pos, addr); - - if (ret) { - PARSER_ERR(s, "invalid address"); - } - - return ret; -} - -static int -parse_servers(ParserState *s) -{ - EXPECT(TOKEN_LBRACE, s, return -1); - get_token(s); - - while (1) { - token_t t = peek_token(s); - if (t == TOKEN_RBRACE) { - get_token(s); - return 0; - } else { - EXPECT(TOKEN_STR, s, return -1); - if (parse_server(s)) - return -1; - } - } - - return 0; -} - -static int -parse_rule(ParserState *s) -{ - return parse_subj(s) || parse_op(s) || parse_pattern(s) || parse_servers(s); -} - -static int -parse_rules(ParserState *s) -{ - int ret; - - while (peek_token(s)) { - if (s->tbl->rules_n >= ARRAY_SIZE(s->tbl->rules)) { - PARSER_ERR(s, "max number of rules reached"); - return -1; - } - - s->rule = &s->tbl->rules[s->tbl->rules_n++]; - - ret = parse_rule(s); - if (ret) - return ret; - } - - return 0; -} - -int run_parser(const char *input, RuleTbl *tbl) -{ - ParserState s = { - .pos = input, - .tbl = tbl - }; - - return parse_rules(&s); -} - -static int -sysctl_handle_rules(ctl_table *ctl, int write, void __user *user_buf, - size_t *lenp, loff_t *ppos) -{ - int ret = 0; - int len = 0; - char *buf = NULL; - RuleTbl *tbl = NULL; - - if (write) { - buf = kzalloc(ctl->maxlen + 1, GFP_KERNEL); - tbl = kzalloc(sizeof(*tbl), GFP_KERNEL); - if (!buf || !tbl) { - ERR("Can't allocate memory\n"); - ret = -ENOMEM; - goto out; - } - - len = min((size_t)ctl->maxlen, *lenp); - ret = copy_from_user(buf, user_buf, len); - if (ret) { - ERR("Can't copy data from user-space\n"); - goto out; - } - - ret = run_parser(buf, tbl); - if (ret) { - ERR("Can't parse input data\n"); - ret = -EINVAL; - goto out; - } - - ret = apply_new_rules(tbl); - if (ret) { - ERR("Can't apply new matching rules\n"); - goto out; - } - } - - ret = proc_dostring(ctl, write, user_buf, lenp, ppos); - if (!ret) - goto out; - -out: - kfree(buf); - kfree(tbl); - - return ret; -} - -static char ctl_data[RULES_TEXT_BUF_SIZE]; - -static ctl_table sched_match_tbl[] = { - { - .procname = "sched_match", - .data = ctl_data, - .maxlen = RULES_TEXT_BUF_SIZE, - .mode = 0644, - .proc_handler = sysctl_handle_rules - }, - {} -}; - -static struct ctl_table_header *sched_match_ctl; - -int -sysctl_register(void) -{ - sched_match_ctl = register_net_sysctl(&init_net, "net/tempesta", - sched_match_tbl); - - return sched_match_ctl ? 0 : 1; -} - -void -sysctl_unregister(void) -{ - unregister_net_sysctl_table(sched_match_ctl); -} diff --git a/tempesta_fw/sched/rr/Makefile b/tempesta_fw/sched/rr/Makefile deleted file mode 100644 index d9dee645dc..0000000000 --- a/tempesta_fw/sched/rr/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# Tempesta FW -# -# Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, -# or (at your option) any later version. -# -# This program 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 59 -# Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -EXTRA_CFLAGS = -DDEBUG -I$(src)/../../ -I$(src)/../../../tempesta_db -I$(src)/../../../sync_socket - -obj-m = tfw_sched_rr.o diff --git a/tempesta_fw/sched/dummy/tfw_sched_dummy.c b/tempesta_fw/sched/tfw_sched_dummy.c similarity index 100% rename from tempesta_fw/sched/dummy/tfw_sched_dummy.c rename to tempesta_fw/sched/tfw_sched_dummy.c diff --git a/tempesta_fw/sched/tfw_sched_http.c b/tempesta_fw/sched/tfw_sched_http.c new file mode 100644 index 0000000000..1d0b432a7b --- /dev/null +++ b/tempesta_fw/sched/tfw_sched_http.c @@ -0,0 +1,842 @@ +/** + * Tempesta FW + * + * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + +#include "http.h" +#include "sched.h" +#include "server.h" +#include "http_match.h" +#include "lib.h" + +MODULE_AUTHOR(TFW_AUTHOR); +MODULE_DESCRIPTION("Tempesta HTTP scheduler"); +MODULE_VERSION("0.0.1"); +MODULE_LICENSE("GPL"); + +#define SH_BANNER "tfw_sched_http: " +#define SH_ERR(...) TFW_ERR(SH_BANNER __VA_ARGS__) +#define SH_LOG(...) TFW_LOG(SH_BANNER __VA_ARGS__) +#define SH_DBG(...) TFW_DBG(SH_BANNER __VA_ARGS__) + +#define MAX_SRV_PER_RULE 16 +#define RULES_TEXT_BUF_SIZE 4096 +#define IP_ADDR_TEXT_BUF_SIZE 32 + +typedef struct { + atomic_t counter; + short n; + short max; + void *ptrs[0]; +} PtrSet; + +#define PTR_SET_SIZE(max) (sizeof(PtrSet) + ((max) * sizeof(void *))) + +typedef struct { + union { + PtrSet *servers; /* Contains TfwServer pointers. */ + PtrSet *addrs; /* Contains TfwAddr pointers. */ + }; + TfwHttpMatchRule rule; +} MatchEntry; + +static TfwHttpMatchList __rcu *match_list; +DEFINE_SPINLOCK(match_list_update_lock); + +static TfwHttpMatchList *saved_rules; +DEFINE_SPINLOCK(saved_rules_lock); + +static PtrSet *added_servers; +DEFINE_SPINLOCK(added_servers_lock); + +/* + * -------------------------------------------------------------------------- + * PtrSet related functions. + * -------------------------------------------------------------------------- + */ + +static int +ptrset_find(const PtrSet *s, const void *ptr) +{ + int i; + + BUG_ON(!s || !ptr); + + for (i = 0; i < s->n; ++i) { + if (s->ptrs[i] == ptr) + return i; + } + + return -1; +} + +static int +ptrset_add(PtrSet *s, void *ptr) +{ + BUG_ON(!s || !ptr); + + if (ptrset_find(s, ptr) > 0) { + SH_ERR("Can't add ptr %p to set %p - duplicate ptr\n", ptr, s); + return -EEXIST; + } + else if (s->n >= s->max) { + SH_ERR("Can't add ptr %p to set %p - set is full\n", ptr, s); + return -ENOSPC; + } + + s->ptrs[s->n] = ptr; + ++s->n; + + return 0; +} + +static int +ptrset_del(PtrSet *s, void *ptr) +{ + int i; + + BUG_ON(!s || !ptr); + + i = ptrset_find(s, ptr); + + if (i < 0) { + SH_ERR("Can't delete ptr %p from set %p - not found\n", ptr, s); + return -ENOENT; + } + + s->ptrs[i] = s->ptrs[s->n - 1]; + s->ptrs[s->n] = NULL; + --s->n; + + return 0; +} + +static void * +ptrset_get_rr(PtrSet *s) +{ + unsigned int n, counter; + void *ret; + + BUG_ON(!s); + + do { + n = s->n; + if (!n) { + SH_ERR("Can't get a pointer from the empty set: %p\n", + s); + return NULL; + } + + counter = atomic_inc_return(&s->counter); + ret = s->ptrs[counter % n]; + } while (!ret); + + return ret; +} + +TfwServer * +tfw_sched_http_get_srv(TfwMsg *msg) +{ + TfwHttpMatchList *mlst; + MatchEntry *entry; + TfwServer *srv = NULL; + + rcu_read_lock(); + mlst = rcu_dereference(match_list); + if (!mlst) { + SH_ERR("No rules loaded to the scheduler\n"); + } else { + entry = tfw_http_match_req_entry((TfwHttpReq * )msg, mlst, + MatchEntry, rule); + if (entry) + srv = ptrset_get_rr(entry->servers); + } + rcu_read_unlock(); + + if (!srv) + SH_ERR("A matching server is not found\n"); + + return srv; +} + +static PtrSet * +alloc_servers(TfwPool *pool) +{ + size_t size = PTR_SET_SIZE(MAX_SRV_PER_RULE); + PtrSet *servers = tfw_pool_alloc(pool, size); + if (!servers) { + SH_ERR("Can't allocate memory from pool: %p\n", pool); + return NULL; + } + memset(servers, 0, size); + servers->max = MAX_SRV_PER_RULE; + + return servers; +} + +#define alloc_addrs(pool) alloc_servers(pool) + +TfwServer * +resolve_addr(TfwAddr *addr) +{ + int i, ret; + TfwAddr curr_addr; + TfwServer *curr_srv; + TfwServer *out_srv = NULL; + + spin_lock_bh(&added_servers_lock); + + for (i = 0; i < added_servers->n; ++i) { + curr_srv = added_servers->ptrs[i]; + + ret = tfw_server_get_addr(curr_srv, &curr_addr); + if (ret) { + SH_ERR("Can't obtain address of the server: %p\n", + curr_srv); + goto out; + } + + if (tfw_addr_eq(addr, &curr_addr)) { + out_srv = curr_srv; + goto out; + } + } + + out: + spin_unlock_bh(&added_servers_lock); + + return out_srv; +} + +int +resolve_addresses(PtrSet *dst_servers, const PtrSet *src_addrs) +{ + int i, ret; + TfwAddr *addr; + TfwServer *srv; + + for (i = 0; i < src_addrs->n; ++i) { + addr = src_addrs->ptrs[i]; + srv = resolve_addr(addr); + if (srv) { + ret = ptrset_add(dst_servers, srv); + if (ret) { + SH_ERR("Can't add server\n"); + return -1; + } + } + } + + return 0; +} + +int +build_match_entry(TfwHttpMatchList *dst_mlst, const MatchEntry *src) +{ + int ret = 0; + MatchEntry *dst; + size_t arg_len; + + arg_len = src->rule.arg.len; + dst = tfw_http_match_entry_new(dst_mlst, MatchEntry, rule, arg_len); + if (!dst) { + SH_ERR("Can't create match entry\n"); + return -1; + } + dst->rule.field = src->rule.field; + dst->rule.op = src->rule.op; + dst->rule.arg.len = arg_len; + memcpy(dst->rule.arg.str, src->rule.arg.str, arg_len); + + dst->servers = alloc_servers(dst_mlst->pool); + if (!dst->servers) + return -1; + + ret = resolve_addresses(dst->servers, src->addrs); + if (ret) + return -1; + + return ret; +} + +static TfwHttpMatchList * +build_match_list(void) +{ + int ret; + TfwHttpMatchList *new_match_list = NULL; + MatchEntry *src_entry; + + new_match_list = tfw_http_match_list_alloc(); + if (!new_match_list) { + SH_ERR("Can't allocate new match list\n"); + return NULL; + } + + spin_lock_bh(&saved_rules_lock); + list_for_each_entry(src_entry, &saved_rules->list, rule.list) + { + ret = build_match_entry(new_match_list, src_entry); + if (ret) { + spin_unlock_bh(&saved_rules_lock); + SH_ERR("Can't build match entry\n"); + tfw_http_match_list_free(new_match_list); + return NULL; + } + } + spin_unlock_bh(&saved_rules_lock); + + return new_match_list; +} + +static int +rebuild_match_list(void) +{ + TfwHttpMatchList *old_match_list, *new_match_list; + + SH_DBG("Refreshing match list\n"); + + if (!saved_rules) { + SH_DBG("No rules loaded to the scheduler yet\n"); + return 0; + } + + new_match_list = build_match_list(); + if (!new_match_list) { + SH_ERR("Can't build new matching list\n"); + return -1; + } + + spin_lock_bh(&match_list_update_lock); + old_match_list = match_list; + rcu_assign_pointer(match_list, new_match_list); + spin_unlock_bh(&match_list_update_lock); + + if (old_match_list) + call_rcu(&old_match_list->rcu, tfw_http_match_list_rcu_free); + + return 0; +} + +int +tfw_sched_http_add_srv(TfwServer *srv) +{ + int ret = 0; + + SH_DBG("Adding server: %p\n", srv); + + spin_lock_bh(&added_servers_lock); + ret = ptrset_add(added_servers, srv); + spin_unlock_bh(&added_servers_lock); + + if (ret) + SH_ERR("Can't add the server to the scheduler: %p\n", srv); + else + rebuild_match_list(); + + return ret; +} + +int +tfw_sched_http_del_srv(TfwServer *srv) +{ + int ret = 0; + + SH_DBG("Deleting server: %p\n", srv); + + spin_lock_bh(&added_servers_lock); + ret = ptrset_del(added_servers, srv); + spin_unlock_bh(&added_servers_lock); + + if (ret) + SH_ERR("Can't delete the server from the scheduler: %p\n", srv); + else + rebuild_match_list(); + + return ret; +} + +/* + * -------------------------------------------------------------------------- + * Sysctl config parser. + * -------------------------------------------------------------------------- + * + * This is a tiny recursive descent parser that tries to mimic this grammar: + * input ::= rules + * rules ::= rule rules + * rule ::= field + * | op + * | arg + * | LBRACE + * | addrs + * | RBRACE + * addrs ::= addr addrs + * addr ::= STR + * field ::= STR + * op ::= STR + * arg ::= STR + * + * The parser is a subject to change. + * In future, it should be generalized to a Tempesta configuration framework. + */ + +typedef enum { + TOKEN_NA = 0, + TOKEN_LBRACE, + TOKEN_RBRACE, + TOKEN_STR, +} token_t; + +static const char *token_str_tbl[] = { + [TOKEN_NA] = STRINGIFY(TOKEN_NA), + [TOKEN_LBRACE] = STRINGIFY(TOKEN_LBRACE), + [TOKEN_RBRACE] = STRINGIFY(TOKEN_RBRACE), + [TOKEN_STR] = STRINGIFY(TOKEN_STR), +}; + +const char * +token_str(token_t t) +{ + BUG_ON(t >= ARRAY_SIZE(token_str_tbl)); + return token_str_tbl[t]; +} + +typedef struct { + token_t token; + int len; + const char *lexeme; + const char *pos; + MatchEntry *entry; + TfwHttpMatchList *mlst; +} ParserState; + +#define PARSER_ERR(s, ...) \ +do { \ + SH_ERR("Parser error: " __VA_ARGS__); \ + SH_ERR("lexeme: %.*s position: %.80s\n", s->len, s->lexeme, s->pos); \ +} while (0) + +token_t +get_token(ParserState *s) +{ + static const token_t single_char_tokens[] = { + [0 ... 255] = TOKEN_NA, + ['{'] = TOKEN_LBRACE, + ['}'] = TOKEN_RBRACE, + }; + const char *p; + + s->token = TOKEN_NA; + s->lexeme = NULL; + s->len = 0; + s->pos = skip_spaces(s->pos); + + if (!s->pos[0]) + goto out; + s->lexeme = s->pos; + + s->token = single_char_tokens[(u8)s->pos[0]]; + if (s->token) { + s->pos++; + s->len = 1; + goto out; + } + + if (s->lexeme[0] == '"') { + for (p = s->pos + 1; *p; ++p) { + if (*p == '"' && *(p - 1) != '\\') { + break; + } + } + if (*p == '"') { + s->lexeme++; + s->len = (p - s->lexeme); + s->pos = ++p; + s->token = TOKEN_STR; + } else { + PARSER_ERR(s, "unterminated quote"); + } + } else { + for (p = s->pos + 1; *p && !isspace(*p); ++p) + ; + s->len = (p - s->pos); + s->pos = p; + s->token = TOKEN_STR; + } + + out: + return s->token; +} + +static token_t +peek_token(ParserState *s) +{ + ParserState old_state = *s; + token_t t = get_token(s); + *s = old_state; + + return t; +} + +#define EXPECT(token, s, action_if_unexpected) \ +do { \ + token_t _t = peek_token(s); \ + if (_t != token) { \ + PARSER_ERR(s, "Unexpected token: %s (expected %s)\n", \ + token_str(_t), token_str(token)); \ + action_if_unexpected; \ + } \ +} while (0) + +#define EXPECT_EITHER(t1, t2, s, action_if_unexpected) \ +({ \ + token_t _t = peek_token(s); \ + if (_t != t1 && _t != t2) { \ + PARSER_ERR(s, "Unexpected token: %s (expected: %s or %s)\n", \ + token_str(_t) token_str(t1), token_str(t2)); \ + action_if_unexpected; \ + } \ + _t; \ +}) + +#define IDX_BY_STR(array, str, maxlen) \ +({ \ + int _found_idx = 0; \ + int _i; \ + for (_i = 0; _i < ARRAY_SIZE(array); ++_i) { \ + if (!array[_i]) \ + continue; \ + if (!strncmp(str, array[_i], maxlen)) { \ + _found_idx = _i; \ + break; \ + } \ + } \ + _found_idx; \ +}) + +static int +parse_field(ParserState *s) +{ + static const char *field_str_tbl[] = { + [TFW_HTTP_MATCH_F_NA] = STRINGIFY(TFW_HTTP_MATCH_F_NA), + [TFW_HTTP_MATCH_F_HOST] = "host", + [TFW_HTTP_MATCH_F_URI] = "uri", + [TFW_HTTP_MATCH_F_HEADERS] = "headers", + }; + tfw_http_match_fld_t field; + + EXPECT(TOKEN_STR, s, return -1); + get_token(s); + + field = IDX_BY_STR(field_str_tbl, s->lexeme, s->len); + if (!field) { + PARSER_ERR(s, "invalid HTTP request field"); + return -1; + } + + s->entry->rule.field = field; + + return 0; +} + +static int +parse_op(ParserState *s) +{ + static const char *op_str_tbl[] = { + [TFW_HTTP_MATCH_O_NA] = STRINGIFY(TFW_HTTP_MATCH_O_NA), + [TFW_HTTP_MATCH_O_EQ] = "=", + [TFW_HTTP_MATCH_O_PREFIX] = "^", + }; + tfw_http_match_op_t op; + + EXPECT(TOKEN_STR, s, return -1); + get_token(s); + + op = IDX_BY_STR(op_str_tbl, s->lexeme, s->len); + if (!op) { + PARSER_ERR(s, "invalid operator"); + return -1; + } + + s->entry->rule.op = op; + + return 0; +} + +static int +parse_arg(ParserState *s) +{ + TfwMatchArg *arg; + size_t old_size, new_size; + + EXPECT(TOKEN_STR, s, return -1); + get_token(s); + + old_size = TFW_HTTP_MATCH_CONT_SIZE(MatchEntry, 0); + new_size = TFW_HTTP_MATCH_CONT_SIZE(MatchEntry, s->len + 1); + s->entry = tfw_pool_realloc(s->mlst->pool, s->entry, old_size, + new_size); + if (!s->entry) { + PARSER_ERR(s, "can't reallocate match entry"); + return -1; + } + + arg = &s->entry->rule.arg; + arg->len = s->len; + memcpy(arg->str, s->lexeme, arg->len); + arg->str[arg->len] = '\0'; + + return 0; +} + +static int +parse_addr(ParserState *s) +{ + int ret = 0; + TfwAddr *addr; + char *pos; + char buf[IP_ADDR_TEXT_BUF_SIZE + 1]; + + EXPECT(TOKEN_STR, s, return -1); + get_token(s); + + addr = tfw_pool_alloc(s->mlst->pool, sizeof(*addr)); + if (!addr) { + PARSER_ERR(s, "can't allocate memory for new address"); + return -1; + } + + memcpy(buf, s->lexeme, s->len); + buf[s->len] = '\0'; + pos = buf; + ret = tfw_inet_pton(&pos, addr); + + if (ret) { + PARSER_ERR(s, "invalid address"); + } else { + ret = ptrset_add(s->entry->addrs, addr); + if (ret) + PARSER_ERR(s, "can't save address"); + } + + return ret; +} + +static int +parse_addrs(ParserState *s) +{ + EXPECT(TOKEN_LBRACE, s, return -1); + get_token(s); + + while (1) { + token_t t = peek_token(s); + if (t == TOKEN_RBRACE) { + get_token(s); + return 0; + } else { + EXPECT(TOKEN_STR, s, return -1); + if (parse_addr(s)) + return -1; + } + } + + return 0; +} + +static int +parse_rule(ParserState *s) +{ + return parse_field(s) || parse_op(s) || parse_arg(s) || parse_addrs(s); +} + +static int +parse_rules(ParserState *s) +{ + int ret; + + while (peek_token(s)) { + s->entry = tfw_http_match_entry_new(s->mlst, MatchEntry, rule, + 0); + if (!s->entry) { + PARSER_ERR(s, "can't allocate new match entry"); + return -1; + } + + s->entry->addrs = alloc_addrs(s->mlst->pool); + if (!s->entry->addrs) { + PARSER_ERR(s, "can't allocate addresses list"); + return -1; + } + + ret = parse_rule(s); + if (ret) + return ret; + } + + return 0; +} + +static TfwHttpMatchList * +run_parser(const char *input) +{ + int ret; + TfwHttpMatchList *mlst; + ParserState s = { + .pos = input, + }; + + mlst = tfw_http_match_list_alloc(); + if (!mlst) { + SH_ERR("Can't allocate match list\n"); + return NULL; + } + s.mlst = mlst; + + ret = parse_rules(&s); + if (ret) { + tfw_http_match_list_free(mlst); + mlst = NULL; + } + + return mlst; +} + +static int +handle_sysctl(ctl_table *ctl, int write, void __user *user_buf, + size_t *lenp, + loff_t *ppos) +{ + int len; + int ret = 0; + TfwHttpMatchList *old_rules, *new_rules; + + if (!write) { + ret = proc_dostring(ctl, write, user_buf, lenp, ppos); + if (ret) + SH_ERR("Can't copy data to user-space\n"); + return ret; + } + + SH_DBG("Copying data to kernel-space\n"); + len = min((size_t )ctl->maxlen, *lenp); + ret = copy_from_user(ctl->data, user_buf, len); + if (ret) { + SH_ERR("Can't copy data from user-space"); + return -1; + } + + SH_DBG("Parsing copied data\n"); + new_rules = run_parser(ctl->data); + if (!new_rules) { + SH_ERR("Can't parse input data\n"); + return -EINVAL; + } + + SH_DBG("Replacing old rules\n"); + spin_lock_bh(&saved_rules_lock); + old_rules = saved_rules; + saved_rules = new_rules; + spin_unlock_bh(&saved_rules_lock); + + tfw_http_match_list_free(old_rules); + + SH_DBG("Applying new rules\n"); + ret = rebuild_match_list(); + if (ret) + SH_ERR("Can't apply new rules\n"); + + return ret; +} + +static struct ctl_table_header *sched_http_ctl; +static char sched_http_ctl_data[RULES_TEXT_BUF_SIZE + 1]; +static ctl_table sched_http_ctl_tbl[] = { + { + .procname = "sched_http_rules", + .data = sched_http_ctl_data, + .maxlen = RULES_TEXT_BUF_SIZE, + .mode = 0644, + .proc_handler = handle_sysctl + }, + { } +}; + +static TfwScheduler tfw_sched_rr_mod = { + .name = "http", + .get_srv = tfw_sched_http_get_srv, + .add_srv = tfw_sched_http_add_srv, + .del_srv = tfw_sched_http_del_srv +}; + +int +tfw_sched_http_init(void) +{ + int ret; + + SH_LOG("init\n"); + + added_servers = kzalloc(PTR_SET_SIZE(TFW_SCHED_MAX_SERVERS), + GFP_KERNEL); + if (!added_servers) { + SH_LOG("Can't allocate servers list\n"); + ret = -ENOMEM; + goto err_alloc; + } + added_servers->max = TFW_SCHED_MAX_SERVERS; + + sched_http_ctl = register_net_sysctl(&init_net, "net/tempesta", + sched_http_ctl_tbl); + if (!sched_http_ctl) { + ret = -1; + SH_LOG("Can't register the sysctl table\n"); + goto err_sysctl_register; + } + + ret = tfw_sched_register(&tfw_sched_rr_mod); + if (ret) { + SH_LOG("Can't register the scheduler module\n"); + ret = -1; + goto err_mod_register; + } + + return ret; + + err_mod_register: + unregister_net_sysctl_table(sched_http_ctl); + err_sysctl_register: + kfree(added_servers); + err_alloc: + return ret; +} + +void +tfw_sched_http_exit(void) +{ + tfw_sched_unregister(); + unregister_net_sysctl_table(sched_http_ctl); + kfree(added_servers); +} + +module_init(tfw_sched_http_init); +module_exit(tfw_sched_http_exit); + diff --git a/tempesta_fw/sched/rr/tfw_sched_rr.c b/tempesta_fw/sched/tfw_sched_rr.c similarity index 100% rename from tempesta_fw/sched/rr/tfw_sched_rr.c rename to tempesta_fw/sched/tfw_sched_rr.c diff --git a/tempesta_fw/t/test_http_match.c b/tempesta_fw/t/test_http_match.c index e1deef1beb..508c6b3899 100644 --- a/tempesta_fw/t/test_http_match.c +++ b/tempesta_fw/t/test_http_match.c @@ -63,9 +63,8 @@ test_mlst_add(int test_id, tfw_http_match_fld_t req_field, e->test_id = test_id; e->rule.field = req_field; e->rule.op = op; - e->rule.arg.str.len = len; - e->rule.arg.str.s = e->rule.extra; - memcpy(&e->rule.extra, str_arg, len); + e->rule.arg.len = len; + memcpy(e->rule.arg.str, str_arg, len); } int @@ -93,9 +92,9 @@ TEST(tfw_http_match_req, returns_first_matching_rule) const TfwHttpMatchRule *match; TfwHttpMatchRule *r1, *r2, *r3; - r1 = tfw_http_match_rule_new(test_mlst, 0); - r2 = tfw_http_match_rule_new(test_mlst, 0); - r3 = tfw_http_match_rule_new(test_mlst, 0); + 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->field = r2->field = r3->field = TFW_HTTP_MATCH_F_METHOD; r1->op = r2->op = r3->op = TFW_HTTP_MATCH_O_EQ;