From 06cfd7581db318f52c7d34a30badcd9c19e10fba Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Thu, 19 Feb 2015 15:01:01 +0300 Subject: [PATCH 01/28] rcl: fix RclAccount search in rcl_account_do() There is a bug in the code that searches RclAccount by the IP address. It walks over the hash bucket, but actually it stops on a matching element. When the loop exits, the result is always NULL, so a new RclAccount object is allocated on every call of the rcl_account_do(). The change simply adds a "break" that stops the loop when a matching element is reached. --- tempesta_fw/classifier/req_conn_limit.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index a28554f81..cb6fad3b6 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -118,12 +118,11 @@ rcl_account_do(struct sock *sk, int (*func)(RclAccount *ra, struct sock *sk)) spin_lock(&hb->lock); hlist_for_each_entry_safe(ra, tmp, &hb->list, hentry) { - if (!ipv6_addr_equal(&addr, &ra->addr)) { - /* Collect garbage. */ - if (ra->last_ts + GC_TO < jiffies / HZ) - hash_del(&ra->hentry); - continue; - } + if (ipv6_addr_equal(&addr, &ra->addr)) + break; + /* Collect garbage. */ + if (ra->last_ts + GC_TO < jiffies / HZ) + hash_del(&ra->hentry); } if (!ra) { spin_unlock(&hb->lock); From b421e443865963716e10a5b6e92678a8f2a54b2c Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Fri, 20 Feb 2015 18:55:18 +0300 Subject: [PATCH 02/28] rcl: add limits for sizes of uri/headers/body of a HTTP request The change enhances the req_conn_limit module with hooks that limit the maximum length for uri/headers/body in a HTTP request. The new sysctl settings are available: - http_uri_len - the limit for the URI length - http_field_len - the limit for the length of a single header field - http_body_len - the limit for the body of a HTTP request All these limits are checked when for every received chunk of data. That allows to drop a HTTP request just after it exceeds a limit without waiting until it is fully received. --- tempesta_fw/classifier/req_conn_limit.c | 187 +++++++++++++++++++++--- tempesta_fw/gfsm.h | 5 +- tempesta_fw/http.h | 6 + 3 files changed, 176 insertions(+), 22 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index cb6fad3b6..a76add571 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -39,6 +39,7 @@ #include "../client.h" #include "../connection.h" #include "../gfsm.h" +#include "../http.h" #include "../log.h" MODULE_AUTHOR(TFW_AUTHOR); @@ -87,6 +88,11 @@ static unsigned int rcl_conn_rate = 0; static unsigned int rcl_conn_burst = 0; static unsigned int rcl_conn_max = 0; +/* Limits for HTTP request contents: uri, headers, body, etc. */ +static unsigned int rcl_http_uri_len = 0; +static unsigned int rcl_http_field_len = 0; +static unsigned int rcl_http_body_len = 0; + static void rcl_get_ipv6addr(struct sock *sk, struct in6_addr *addr) { @@ -252,12 +258,90 @@ rcl_req_limit(RclAccount *ra, struct sock *sk) return TFW_BLOCK; } +static int +rcl_http_uri_len_limit(const TfwHttpReq *req) +{ + /* FIXME: tfw_str_len() iterates over chunks to calculate the length. + * This is too slow. The value must be stored in a TfwStr field. */ + if (rcl_http_uri_len && tfw_str_len(&req->uri) > rcl_http_uri_len) { + TFW_DBG("rcl: http_uri_len limit is reached\n"); + return TFW_BLOCK; + } + + return TFW_PASS; +} + +static int +rcl_http_field_len_limit(const TfwHttpReq *req) +{ + const TfwStr *field, *end; + + if (!rcl_http_field_len) + return TFW_PASS; + + TFW_HTTP_FOR_EACH_HDR_FIELD(field, end, req) { + if (tfw_str_len(field) > rcl_http_field_len) { + TFW_DBG("rcl: http_field_len limit is reached\n"); + return TFW_BLOCK; + } + } + + return TFW_PASS; +} + +static int +rcl_http_body_len_limit(const TfwHttpReq *req) +{ + if (rcl_http_body_len && tfw_str_len(&req->body) > rcl_http_body_len) { + TFW_DBG("rcl: http_body_len limit is reached\n"); + return TFW_BLOCK; + } + + return TFW_PASS; +} + +static int +rcl_http_len_limit(const TfwHttpReq *req) +{ + int r; + + r = rcl_http_uri_len_limit(req); + if (r) + return r; + r = rcl_http_field_len_limit(req); + if (r) + return r; + r = rcl_http_body_len_limit(req); + if (r) + return r; + + return 0; +} + static int rcl_http_req_handler(void *obj, unsigned char *data, size_t len) +{ + int r; + TfwConnection *c = (TfwConnection *)obj; + TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); + + r = rcl_account_do(c->sess->cli->sock, rcl_req_limit); + if (r) + return r; + r = rcl_http_len_limit(req); + if (r) + return r; + + return 0; +} + +static int +rcl_http_chunk_handler(void *obj, unsigned char *data, size_t len) { TfwConnection *c = (TfwConnection *)obj; + TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); - return rcl_account_do(c->sk, rcl_req_limit); + return rcl_http_len_limit(req); } static TfwClassifier rcl_class_ops = { @@ -305,6 +389,9 @@ char rcl_req_burst_str[RCL_INT_LEN]; char rcl_conn_rate_str[RCL_INT_LEN]; char rcl_conn_burst_str[RCL_INT_LEN]; char rcl_conn_max_str[RCL_INT_LEN]; +char rcl_http_uri_len_str[RCL_INT_LEN]; +char rcl_http_field_len_str[RCL_INT_LEN]; +char rcl_http_body_len_str[RCL_INT_LEN]; static ctl_table rcl_ctl_table[] = { { @@ -347,6 +434,30 @@ static ctl_table rcl_ctl_table[] = { .proc_handler = rcl_sysctl_int, .extra1 = &rcl_conn_max, }, + { + .procname = "http_uri_len", + .data = rcl_http_uri_len_str, + .maxlen = 10, + .mode = 0644, + .proc_handler = rcl_sysctl_int, + .extra1 = &rcl_http_uri_len, + }, + { + .procname = "http_field_len", + .data = rcl_http_field_len_str, + .maxlen = 10, + .mode = 0644, + .proc_handler = rcl_sysctl_int, + .extra1 = &rcl_http_field_len, + }, + { + .procname = "http_body_len", + .data = rcl_http_body_len_str, + .maxlen = 10, + .mode = 0644, + .proc_handler = rcl_sysctl_int, + .extra1 = &rcl_http_body_len, + }, {} }; static struct ctl_path __tfw_path[] = { @@ -366,44 +477,80 @@ rcl_init(void) return -EINVAL; } + rcl_ctl = register_net_sysctl(&init_net, "net/tempesta/req_conn_limit", + rcl_ctl_table); + if (!rcl_ctl) { + TFW_ERR("rcl: can't register sysctl table\n"); + r = -1; + goto err_sysctl; + } + r = tfw_classifier_register(&rcl_class_ops); if (r) { TFW_ERR("rcl: can't register classifier\n"); goto err_class; } - r = tfw_gfsm_register_fsm(TFW_FSM_RCL, rcl_http_req_handler); + /** + * FIXME: + * Here we add two primitive hooks by registering two FSMs. + * There is a bunch of problems here: + * - We can't unregister hooks. Therefore, we can't unload this module + * and can't recover if the second tfw_gfsm_register_hook() fails. + * - We have to add every hook to the global enum of FSMs. + * - We register two FSMs, but actually don't implement anything close + * to FSM and don't need the FSM switching logic. + * + * The suggested solution is to extend the GFSM with the support of + * "lightweight hooks" that behave like plain functions rather than FSM. + * That should solve all these problems listed above: + * - Such hook may be unregistered any time it is not executed. + * - The hook doesn't need an ID, so no need to maintain a global list + * of all known hooks in the GFSM code. + * - No need to create a dummy FSM just to call the hook. + * This is easy to comprehend, and perhaps faster since the GFSM + * doesn't need to switch FSMs. + */ + r = tfw_gfsm_register_fsm(TFW_FSM_RCL_REQ, rcl_http_req_handler); if (r) { - TFW_ERR("rcl: can't register fsm\n"); - goto err_fsm; + TFW_ERR("rcl: can't register fsm: req\n"); + goto err_fsm_req; } - - rcl_ctl = register_sysctl_paths(__tfw_path, rcl_ctl_table); - if (!rcl_ctl) { - TFW_ERR("rcl: can't register sysctl table\n"); - goto err_sysctl; + r = tfw_gfsm_register_fsm(TFW_FSM_RCL_CHUNK, rcl_http_chunk_handler); + if (r) { + TFW_ERR("rcl: can't register fsm: chunk\n"); + goto err_fsm_chunk; } - /* Must be last call - we can't unregister the hook. */ r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_REQ_MSG, TFW_FSM_RCL, 0); - - if (r < 0) { - TFW_ERR("rcl: can't register gfsm hook\n"); - goto err_hook; + TFW_HTTP_FSM_REQ_MSG, 0, + TFW_FSM_RCL_REQ); + if (r) { + TFW_ERR("rcl: can't register gfsm hook: req\n"); + goto err_hook_req; + } + r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, + TFW_HTTP_FSM_REQ_CHUNK, 0, + TFW_FSM_RCL_CHUNK); + if (r) { + TFW_ERR("rcl: can't register gfsm hook: chunk\n"); + TFW_ERR("rcl: can't recover\n"); + BUG(); } TFW_WARN("rcl mudule can't be unloaded, " "so all allocated resources won't freed\n"); return 0; -err_hook: - unregister_sysctl_table(rcl_ctl); -err_sysctl: - tfw_gfsm_unregister_fsm(TFW_FSM_RCL); -err_fsm: +err_hook_req: + tfw_gfsm_unregister_fsm(TFW_FSM_RCL_CHUNK); +err_fsm_chunk: + tfw_gfsm_unregister_fsm(TFW_FSM_RCL_REQ); +err_fsm_req: tfw_classifier_unregister(); err_class: + unregister_sysctl_table(rcl_ctl); +err_sysctl: kmem_cache_destroy(rcl_mem_cache); return r; } diff --git a/tempesta_fw/gfsm.h b/tempesta_fw/gfsm.h index e6f35e254..7d65e03bf 100644 --- a/tempesta_fw/gfsm.h +++ b/tempesta_fw/gfsm.h @@ -81,9 +81,10 @@ enum { TFW_FSM_HTTPS, /* Request connection limiting classifier */ - TFW_FSM_RCL, + TFW_FSM_RCL_REQ, + TFW_FSM_RCL_CHUNK, - TFW_FSM_NUM = TFW_GFSM_FSM_N + TFW_FSM_NUM /* Must be <= TFW_GFSM_FSM_N */ }; #define TFW_FSM_TYPE(t) ((t) & TFW_GFSM_FSM_MASK) diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 8c74a2cf0..0b3e81292 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -163,6 +163,12 @@ typedef struct { unsigned int expires; } TfwHttpResp; +#define TFW_HTTP_FOR_EACH_HDR_FIELD(pos, end, msg) \ + for ((pos) = &(msg)->h_tbl->tbl[0].field, \ + (end) = &(msg)->h_tbl->tbl[(msg)->h_tbl->off].field; \ + (pos) < (end); \ + ++(pos)) + typedef void (*tfw_http_req_cache_cb_t)(TfwHttpReq *, TfwHttpResp *, void *); /* Internal (parser) HTTP functions. */ From a3a9a6fc17807b4946b0f63a4a8341db6647df25 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Sun, 22 Feb 2015 06:21:10 +0300 Subject: [PATCH 03/28] rcl: add http_methods - the list of allowed HTTP methods The new sysctl variable 'http_methods' allows to specify the list of allowed HTTP methods. Requests with other methods are blocked. Only methods supported by the HTTP parser can be specified in the list. --- tempesta_fw/classifier/req_conn_limit.c | 141 +++++++++++++++++++++++- tempesta_fw/http.h | 7 +- 2 files changed, 144 insertions(+), 4 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index a76add571..cdb2de5ca 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -92,6 +92,7 @@ static unsigned int rcl_conn_max = 0; static unsigned int rcl_http_uri_len = 0; static unsigned int rcl_http_field_len = 0; static unsigned int rcl_http_body_len = 0; +static unsigned long rcl_http_methods_mask = 0; static void rcl_get_ipv6addr(struct sock *sk, struct in6_addr *addr) @@ -318,6 +319,18 @@ rcl_http_len_limit(const TfwHttpReq *req) return 0; } +static int +rcl_http_methods_check(const TfwHttpReq *req) +{ + unsigned long m = (1 << req->method); + + if (rcl_http_methods_mask && (rcl_http_methods_mask & m)) + return TFW_PASS; + + TFW_DBG("rcl: forbidden method: %d (%#lx)\n", req->method, m); + return TFW_BLOCK; +} + static int rcl_http_req_handler(void *obj, unsigned char *data, size_t len) { @@ -338,10 +351,18 @@ rcl_http_req_handler(void *obj, unsigned char *data, size_t len) static int rcl_http_chunk_handler(void *obj, unsigned char *data, size_t len) { + int r; TfwConnection *c = (TfwConnection *)obj; TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); - return rcl_http_len_limit(req); + r = rcl_http_methods_check(req); + if (r) + return r; + r = rcl_http_len_limit(req); + if (r) + return r; + + return 0; } static TfwClassifier rcl_class_ops = { @@ -382,7 +403,115 @@ rcl_sysctl_int(ctl_table *ctl, int write, void __user *buffer, size_t *lenp, return proc_dostring(ctl, write, buffer, lenp, ppos); } +/* TODO: refactoring: get rid of these sysctl handlers, + * replace them with Tempesta's cfg framework. */ + +static int +rcl_find_idx_by_str(const char **str_vec, size_t str_vec_size, const char *str) +{ + int i; + const char *curr_str; + + for (i = 0; i < str_vec_size; ++i) { + curr_str = str_vec[i]; + BUG_ON(!curr_str); + if (!strcasecmp(curr_str, str)) + return i; + } + + return -1; +} + +static char * +rcl_tokenize(char **tmp_buf) +{ + char *token = strsep(tmp_buf, " \r\n\t"); + + /* Unlike strsep(), don't return empty tokens. */ + if (token && !*token) + token = NULL; + + return token; +} + +static int +rcl_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) +{ + static const char *strs[_TFW_HTTP_METH_COUNT] = { + [TFW_HTTP_METH_GET] = "get", + [TFW_HTTP_METH_POST] = "post", + [TFW_HTTP_METH_HEAD] = "head", + }; + char *token; + int method_idx; + unsigned long methods_mask; + + token = tmp_buf; + methods_mask = 0; + while ((token = rcl_tokenize(&tmp_buf))) { + method_idx = rcl_find_idx_by_str(strs, ARRAY_SIZE(strs), token); + if (method_idx < 0) { + TFW_ERR("rcl: invalid method: '%s'\n", token); + return -EINVAL; + } + + TFW_DBG("rcl: parsed method: %s => %d\n", token, method_idx); + methods_mask |= (1 << method_idx); + } + + TFW_DBG("parsed methods_mask: %#lx\n", methods_mask); + *out_mask = methods_mask; + return 0; +} + +static int +rcl_sysctl_handle(ctl_table *ctl, int write, + void __user *buffer, size_t *lenp, loff_t *ppos) +{ + int r, len; + char *tmp_buf; + void *parse_dest; + int (*parse_fn)(const char *tmp_buf, void *dest); + + tmp_buf = NULL; + parse_fn = ctl->extra1; + parse_dest = ctl->extra2; + BUG_ON(!parse_fn || !parse_dest); + + if (write) { + tmp_buf = kzalloc(ctl->maxlen + 1, GFP_KERNEL); + if (!tmp_buf) { + TFW_ERR("rcl: can't allocate temporary buffer\n"); + r = -ENOMEM; + goto out; + } + + len = min(ctl->maxlen, (int)*lenp); + if (copy_from_user(tmp_buf, buffer, len)) { + TFW_ERR("rcl: can't copy data from userspace\n"); + r = -EFAULT; + goto out; + } + + if (parse_fn(tmp_buf, parse_dest)) { + TFW_ERR("rcl: can't parse input data\n"); + r = -EINVAL; + goto out; + } + } + + r = proc_dostring(ctl, write, buffer, lenp, ppos); + if (r) + TFW_ERR("rcl: sysctl error\n"); +out: + if (r) + TFW_ERR("rcl: can't read/write parameter: %s\n", ctl->procname); + kfree(tmp_buf); + return r; +} + #define RCL_INT_LEN 10 +#define RCL_STR_LEN 255 char rcl_req_rate_str[RCL_INT_LEN]; char rcl_req_burst_str[RCL_INT_LEN]; @@ -392,6 +521,7 @@ char rcl_conn_max_str[RCL_INT_LEN]; char rcl_http_uri_len_str[RCL_INT_LEN]; char rcl_http_field_len_str[RCL_INT_LEN]; char rcl_http_body_len_str[RCL_INT_LEN]; +char rcl_http_methods_str[RCL_STR_LEN]; static ctl_table rcl_ctl_table[] = { { @@ -458,6 +588,15 @@ static ctl_table rcl_ctl_table[] = { .proc_handler = rcl_sysctl_int, .extra1 = &rcl_http_body_len, }, + { + .procname = "http_methods", + .data = rcl_http_methods_str, + .maxlen = RCL_STR_LEN, + .mode = 0644, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_methods_mask, + .extra2 = &rcl_http_methods_mask, + }, {} }; static struct ctl_path __tfw_path[] = { diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 0b3e81292..435625540 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -33,9 +33,10 @@ #define TFW_HTTP_PF_CRLF (TFW_HTTP_PF_CR | TFW_HTTP_PF_LF) typedef enum { - TFW_HTTP_METH_GET = 0, - TFW_HTTP_METH_HEAD = 1, - TFW_HTTP_METH_POST = 2, + TFW_HTTP_METH_GET, + TFW_HTTP_METH_HEAD, + TFW_HTTP_METH_POST, + _TFW_HTTP_METH_COUNT, } tfw_http_meth_t; #define TFW_HTTP_CC_NO_CACHE 0x001 From 267bea066e8e0232947e72472368645a4609254f Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 23 Feb 2015 15:07:01 +0300 Subject: [PATCH 04/28] rcl: get rid of duplicate logic in rcl_sysctl_int() The rcl_syscl_int() contains the same logic as the rcl_sysctl_handle(). Therefore, transform the rcl_syscl_int() into rcl_parse_int() leaving only specific code, which is used in combination with rcl_syscl_handle(). --- tempesta_fw/classifier/req_conn_limit.c | 75 ++++++++++++------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index cdb2de5ca..c3be1a0b1 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -371,36 +371,23 @@ static TfwClassifier rcl_class_ops = { }; static int -rcl_sysctl_int(ctl_table *ctl, int write, void __user *buffer, size_t *lenp, - loff_t *ppos) +rcl_parse_int(char *tmp_buf, int *out_val) { - unsigned int *param = ctl->extra1; + char *p; + int val = 0; - if (write) { - unsigned int tmp_v = 0; - char *p, *tmp_buf; - - tmp_buf = kzalloc(ctl->maxlen + 1, GFP_KERNEL); - if (!tmp_buf) - return -ENOMEM; - if (copy_from_user(tmp_buf, buffer, ctl->maxlen)) { - kfree(tmp_buf); - return -EFAULT; - } + tmp_buf = strim(tmp_buf); - for (p = tmp_buf; *p; ++p) { - if (!isdigit(*p)) { - kfree(tmp_buf); - return -EINVAL; - } - tmp_v = tmp_v * 10 + *p - '0'; + for (p = tmp_buf; *p; ++p) { + if (!isdigit(*p)) { + TFW_ERR("not a digit: '%c'\n", *p); + return -EINVAL; } - *param = tmp_v; - - kfree(tmp_buf); + val = val * 10 + *p - '0'; } - return proc_dostring(ctl, write, buffer, lenp, ppos); + *out_val = val; + return 0; } /* TODO: refactoring: get rid of these sysctl handlers, @@ -529,64 +516,72 @@ static ctl_table rcl_ctl_table[] = { .data = rcl_req_rate_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_req_rate, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_req_rate, }, { .procname = "request_burst", .data = rcl_req_burst_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_req_burst, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_req_burst, }, { .procname = "new_connection_rate", .data = rcl_conn_rate_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_conn_rate, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_conn_rate, }, { .procname = "new_connection_burst", .data = rcl_conn_burst_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_conn_burst, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_conn_burst, }, { .procname = "concurrent_connections", .data = rcl_conn_max_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_conn_max, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_conn_max, }, { .procname = "http_uri_len", .data = rcl_http_uri_len_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_http_uri_len, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_http_uri_len, }, { .procname = "http_field_len", .data = rcl_http_field_len_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_http_field_len, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_http_field_len, }, { .procname = "http_body_len", .data = rcl_http_body_len_str, .maxlen = 10, .mode = 0644, - .proc_handler = rcl_sysctl_int, - .extra1 = &rcl_http_body_len, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_int, + .extra2 = &rcl_http_body_len, }, { .procname = "http_methods", From 27aeaea1e2b460d0473ef0ec2190d4227af2589b Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 23 Feb 2015 15:16:40 +0300 Subject: [PATCH 05/28] rcl: get rid of hardcoded buffer sizes in the sysctl table --- tempesta_fw/classifier/req_conn_limit.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index c3be1a0b1..119699ea4 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -514,7 +514,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "request_rate", .data = rcl_req_rate_str, - .maxlen = 10, + .maxlen = sizeof(rcl_req_rate_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -523,7 +523,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "request_burst", .data = rcl_req_burst_str, - .maxlen = 10, + .maxlen = sizeof(rcl_req_burst_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -532,7 +532,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "new_connection_rate", .data = rcl_conn_rate_str, - .maxlen = 10, + .maxlen = sizeof(rcl_conn_rate_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -541,7 +541,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "new_connection_burst", .data = rcl_conn_burst_str, - .maxlen = 10, + .maxlen = sizeof(rcl_conn_burst_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -550,7 +550,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "concurrent_connections", .data = rcl_conn_max_str, - .maxlen = 10, + .maxlen = sizeof(rcl_conn_max_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -559,7 +559,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "http_uri_len", .data = rcl_http_uri_len_str, - .maxlen = 10, + .maxlen = sizeof(rcl_http_uri_len_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -568,7 +568,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "http_field_len", .data = rcl_http_field_len_str, - .maxlen = 10, + .maxlen = sizeof(rcl_http_field_len_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -577,7 +577,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "http_body_len", .data = rcl_http_body_len_str, - .maxlen = 10, + .maxlen = sizeof(rcl_http_body_len_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_int, @@ -586,7 +586,7 @@ static ctl_table rcl_ctl_table[] = { { .procname = "http_methods", .data = rcl_http_methods_str, - .maxlen = RCL_STR_LEN, + .maxlen = sizeof(rcl_http_methods_str), .mode = 0644, .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_methods_mask, From d5ae2a6637419bbd2867b90d811b83a8b7740c6b Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 23 Feb 2015 16:12:10 +0300 Subject: [PATCH 06/28] xff: add "X-Forwarded-For" string to the http_match code Some time ago, the X-Forarded-For header was made a special header, but it was not added to the hdr_val_eq() as it should be done for all special headers. So this commit fixes that mistake. --- tempesta_fw/http_match.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tempesta_fw/http_match.c b/tempesta_fw/http_match.c index 4ebffe61e..93ba55593 100644 --- a/tempesta_fw/http_match.c +++ b/tempesta_fw/http_match.c @@ -104,8 +104,9 @@ hdr_val_eq(const TfwHttpReq *req, tfw_http_hdr_t id, const char *val, const char *name; int name_len; } hdr_name_tbl[TFW_HTTP_HDR_NUM] = { - [TFW_HTTP_HDR_CONNECTION] = _HDR("Connection"), - [TFW_HTTP_HDR_HOST] = _HDR("Host"), + [TFW_HTTP_HDR_CONNECTION] = _HDR("Connection"), + [TFW_HTTP_HDR_HOST] = _HDR("Host"), + [TFW_HTTP_HDR_X_FORWARDED_FOR] = _HDR("X-Forwarded-For"), }; #undef _HDR From 0e4d867f5b984f54391f23c485516b7a16efa742 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 25 Feb 2015 18:10:23 +0300 Subject: [PATCH 07/28] rcl: add Content-Type check Add two new sysctl settings: - http_ct_is_required 0/1 - require all POST requests to have a "Content-Type" header field. - http_ct_vals - the whitespace-separated list of Content-Type values. The checks are done when the whole HTTP request is arrived. --- tempesta_fw/classifier/req_conn_limit.c | 190 +++++++++++++++++++++++- tempesta_fw/http.h | 8 +- 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index 119699ea4..ad52829f4 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -93,6 +93,16 @@ static unsigned int rcl_http_uri_len = 0; static unsigned int rcl_http_field_len = 0; static unsigned int rcl_http_body_len = 0; static unsigned long rcl_http_methods_mask = 0; +static bool rcl_http_ct_is_required = false; + +/* The list of allowed Content-Type values. */ + +typedef struct { + char *str; + size_t len; /* The pre-computed strlen(@str). */ +} RclCtVal; + +static RclCtVal *rcl_http_ct_vals __rcu; static void rcl_get_ipv6addr(struct sock *sk, struct in6_addr *addr) @@ -331,6 +341,55 @@ rcl_http_methods_check(const TfwHttpReq *req) return TFW_BLOCK; } +static int +rcl_http_ct_check(const TfwHttpReq *req) +{ + TfwStr *ct, *end; + RclCtVal *curr; + + if (!rcl_http_ct_is_required || req->method != TFW_HTTP_METH_POST) + return TFW_PASS; + + /* Find the Content-Type header. + * + * XXX: Should we make the header "special"? + * It would speed up this function, but bloat the HTTP parser code, + * and pollute the headers table. + */ + TFW_HTTP_FOR_EACH_RAW_HDR_FIELD(ct, end, req) { + if (tfw_str_eq_cstr(ct, "Content-Type", sizeof("Content-Type"), + TFW_STR_EQ_PREFIX_CASEI)) + break; + + } + if (ct == end) { + TFW_DBG("rcl: Content-Type is missing\n"); + return TFW_BLOCK; + } + + /* Check that the Content-Type is in the list of allowed values. + * + * TODO: possible improvement: binary search. + * Generally the binary search is more efficient, but linear search is + * usually faster for small sets of values. Perhaps we should switch + * between two if the performance is that critical here, but benchmarks + * should be done to measure the impact. + */ + rcu_read_lock(); + for (curr = rcu_dereference(rcl_http_ct_vals); curr->str; ++curr) { + if (tfw_str_eq_cstr(ct, curr->str, curr->len, TFW_STR_EQ_CASEI)) + break; + } + rcu_read_unlock(); + + if (!curr->str) { + TFW_DBG("rcl: forbidden Content-Type value\n"); + return TFW_BLOCK; + } + + return TFW_PASS; +} + static int rcl_http_req_handler(void *obj, unsigned char *data, size_t len) { @@ -342,6 +401,9 @@ rcl_http_req_handler(void *obj, unsigned char *data, size_t len) if (r) return r; r = rcl_http_len_limit(req); + if (r) + return r; + r = rcl_http_ct_check(req); if (r) return r; @@ -376,8 +438,6 @@ rcl_parse_int(char *tmp_buf, int *out_val) char *p; int val = 0; - tmp_buf = strim(tmp_buf); - for (p = tmp_buf; *p; ++p) { if (!isdigit(*p)) { TFW_ERR("not a digit: '%c'\n", *p); @@ -390,6 +450,21 @@ rcl_parse_int(char *tmp_buf, int *out_val) return 0; } +static int +rcl_parse_bool(char *tmp_buf, bool *out_bool) +{ + char c = *tmp_buf; + + /* XXX: should we support true/false/etc instead of 0/1 here? */ + if (c != '0' && c != '1') { + TFW_ERR("invalid boolean value: %s\n", tmp_buf); + return -EINVAL; + } + + *out_bool = c - '0'; + return 0; +} + /* TODO: refactoring: get rid of these sysctl handlers, * replace them with Tempesta's cfg framework. */ @@ -409,10 +484,12 @@ rcl_find_idx_by_str(const char **str_vec, size_t str_vec_size, const char *str) return -1; } +#define RCL_TOKEN_SEPARATORS " \r\n\t" + static char * rcl_tokenize(char **tmp_buf) { - char *token = strsep(tmp_buf, " \r\n\t"); + char *token = strsep(tmp_buf, RCL_TOKEN_SEPARATORS); /* Unlike strsep(), don't return empty tokens. */ if (token && !*token) @@ -421,6 +498,21 @@ rcl_tokenize(char **tmp_buf) return token; } +static int +rcl_count_tokens(char *str) +{ + int n = 0; + + while (*str) { + str += strspn(str, RCL_TOKEN_SEPARATORS); + if (*str) + ++n; + str += strcspn(str, RCL_TOKEN_SEPARATORS); + } + + return n; +} + static int rcl_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) { @@ -451,19 +543,80 @@ rcl_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) return 0; } +static int +rcl_parse_ct_vals(char *tmp_buf, void *unused) +{ + void *mem; + char *token, *strs, *strs_pos; + size_t tokens_n, vals_size, strs_size; + RclCtVal *vals, *vals_pos, *old_vals; + + tokens_n = rcl_count_tokens(tmp_buf); + if (!tokens_n) { + TFW_ERR("the rcl_http_ct_vals is empty\n"); + return -EINVAL; + } + + /* Allocate a single chunk of memory which is suitable to hold the + * variable-sized list of variable-sized strings. + * + * Basically that will look like: + * [[RclCtVal, RclCtVal, RclCtVal, NULL]string1\0\string2\0\string3\0] + * + + + ^ ^ ^ + * | | | | | | + * +---------------------------+ | | + * | | | | + * +---------------------------+ | + * | | + * +---------------------------+ + */ + strs_size = strlen(tmp_buf) + 1; + vals_size = sizeof(RclCtVal) * (tokens_n + 1); + mem = kzalloc(vals_size + strs_size, GFP_KERNEL); + vals = mem; + strs = mem + vals_size; + + /* Copy tokens from tmp_buf to the vals/strs list. */ + /* TODO: validate tokens, they should look like: "text/plain". */ + vals_pos = vals; + strs_pos = strs; + while ((token = rcl_tokenize(&tmp_buf))) { + size_t len = strlen(token) + 1; + BUG_ON(!len); + + memcpy(strs_pos, token, len); + vals_pos->str = strs_pos; + vals_pos->len = (len - 1); + TFW_DBG("parsed Content-Type value: '%s'\n", strs_pos); + + vals_pos++; + strs_pos += len; + } + BUG_ON(vals_pos != (vals + tokens_n)); + BUG_ON(strs_pos > (strs + strs_size)); + + /* Replace the old list of allowed Content-Type values. */ + /* TODO: sort values to make binary search possible. */ + old_vals = rcl_http_ct_vals; + rcu_assign_pointer(rcl_http_ct_vals, vals); + synchronize_rcu(); + kfree(old_vals); + return 0; +} + static int rcl_sysctl_handle(ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { int r, len; - char *tmp_buf; + char *tmp_buf, *trimmed_buf; void *parse_dest; int (*parse_fn)(const char *tmp_buf, void *dest); tmp_buf = NULL; parse_fn = ctl->extra1; parse_dest = ctl->extra2; - BUG_ON(!parse_fn || !parse_dest); + BUG_ON(!parse_fn); if (write) { tmp_buf = kzalloc(ctl->maxlen + 1, GFP_KERNEL); @@ -480,7 +633,8 @@ rcl_sysctl_handle(ctl_table *ctl, int write, goto out; } - if (parse_fn(tmp_buf, parse_dest)) { + trimmed_buf = strim(tmp_buf); + if (parse_fn(trimmed_buf, parse_dest)) { TFW_ERR("rcl: can't parse input data\n"); r = -EINVAL; goto out; @@ -497,8 +651,9 @@ rcl_sysctl_handle(ctl_table *ctl, int write, return r; } -#define RCL_INT_LEN 10 -#define RCL_STR_LEN 255 +#define RCL_INT_LEN 10 +#define RCL_STR_LEN 255 +#define RCL_LONG_STR_LEN 1024 char rcl_req_rate_str[RCL_INT_LEN]; char rcl_req_burst_str[RCL_INT_LEN]; @@ -509,6 +664,8 @@ char rcl_http_uri_len_str[RCL_INT_LEN]; char rcl_http_field_len_str[RCL_INT_LEN]; char rcl_http_body_len_str[RCL_INT_LEN]; char rcl_http_methods_str[RCL_STR_LEN]; +char rcl_http_ct_is_required_str[RCL_INT_LEN]; +char rcl_http_content_types_str[RCL_LONG_STR_LEN]; static ctl_table rcl_ctl_table[] = { { @@ -592,6 +749,23 @@ static ctl_table rcl_ctl_table[] = { .extra1 = rcl_parse_methods_mask, .extra2 = &rcl_http_methods_mask, }, + { + .procname = "http_ct_is_required", + .data = rcl_http_ct_is_required_str, + .maxlen = sizeof(rcl_http_ct_is_required_str), + .mode = 0644, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_bool, + .extra2 = &rcl_http_ct_is_required, + }, + { + .procname = "http_ct_vals", + .data = rcl_http_content_types_str, + .maxlen = sizeof(rcl_http_content_types_str), + .mode = 0644, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_ct_vals, + }, {} }; static struct ctl_path __tfw_path[] = { diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 435625540..8b3247450 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -165,7 +165,13 @@ typedef struct { } TfwHttpResp; #define TFW_HTTP_FOR_EACH_HDR_FIELD(pos, end, msg) \ - for ((pos) = &(msg)->h_tbl->tbl[0].field, \ + __TFW_HTTP_FOR_EACH_HDR_FROM(pos, end, msg, 0) + +#define TFW_HTTP_FOR_EACH_RAW_HDR_FIELD(pos, end, msg) \ + __TFW_HTTP_FOR_EACH_HDR_FROM(pos, end, msg, TFW_HTTP_HDR_RAW) + +#define __TFW_HTTP_FOR_EACH_HDR_FROM(pos, end, msg, start_off) \ + for ((pos) = &(msg)->h_tbl->tbl[start_off].field, \ (end) = &(msg)->h_tbl->tbl[(msg)->h_tbl->off].field; \ (pos) < (end); \ ++(pos)) From fccf32275ce428b489545e459eda1939020c91af Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 25 Feb 2015 20:41:57 +0300 Subject: [PATCH 08/28] rcl: add the http_host_is_required check It simply checks that a HTTP POST request has a "Host" header (which is required by RFC2616 and RFC7230). --- tempesta_fw/classifier/req_conn_limit.c | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index ad52829f4..6e470123f 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -94,6 +94,7 @@ static unsigned int rcl_http_field_len = 0; static unsigned int rcl_http_body_len = 0; static unsigned long rcl_http_methods_mask = 0; static bool rcl_http_ct_is_required = false; +static bool rcl_http_host_is_required = false; /* The list of allowed Content-Type values. */ @@ -390,6 +391,27 @@ rcl_http_ct_check(const TfwHttpReq *req) return TFW_PASS; } +static int +rcl_http_host_check(const TfwHttpReq *req) +{ + TfwStr *field; + + if (!rcl_http_host_is_required || req->method != TFW_HTTP_METH_POST) + return TFW_PASS; + + field = &req->h_tbl->tbl[TFW_HTTP_HDR_HOST].field; + if (!field->ptr) { + TFW_DBG("rcl: the Host header is missing\n"); + return TFW_BLOCK; + } + + /* FIXME: here should be a check that the Host value is not an IP + * address. Need a fast routine that supports compound TfwStr. + * Perhaps should implement a tiny FSM or postpone the task until we + * have a good regex library. */ + return TFW_PASS; +} + static int rcl_http_req_handler(void *obj, unsigned char *data, size_t len) { @@ -404,6 +426,9 @@ rcl_http_req_handler(void *obj, unsigned char *data, size_t len) if (r) return r; r = rcl_http_ct_check(req); + if (r) + return r; + r = rcl_http_host_check(req); if (r) return r; @@ -666,6 +691,7 @@ char rcl_http_body_len_str[RCL_INT_LEN]; char rcl_http_methods_str[RCL_STR_LEN]; char rcl_http_ct_is_required_str[RCL_INT_LEN]; char rcl_http_content_types_str[RCL_LONG_STR_LEN]; +char rcl_http_host_is_required_str[RCL_INT_LEN]; static ctl_table rcl_ctl_table[] = { { @@ -766,6 +792,15 @@ static ctl_table rcl_ctl_table[] = { .proc_handler = rcl_sysctl_handle, .extra1 = rcl_parse_ct_vals, }, + { + .procname = "http_host_is_required", + .data = rcl_http_host_is_required_str, + .maxlen = sizeof(rcl_http_host_is_required_str), + .mode = 0644, + .proc_handler = rcl_sysctl_handle, + .extra1 = rcl_parse_bool, + .extra2 = &rcl_http_host_is_required + }, {} }; static struct ctl_path __tfw_path[] = { From fbd43919edea7f38143d2d19ae9da0443b8964d4 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 25 Feb 2015 22:11:41 +0300 Subject: [PATCH 09/28] rcl: fix header comparison in rcl_http_ct_check() The old code didn't take into account that the header name is stored together with the value, e.g. "Content-Type: text/html" instead of just "text/html". --- tempesta_fw/classifier/req_conn_limit.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index 6e470123f..0d311332b 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -345,6 +345,8 @@ rcl_http_methods_check(const TfwHttpReq *req) static int rcl_http_ct_check(const TfwHttpReq *req) { +#define _CT "Content-Type" +#define _CTLEN (sizeof(_CT) - 1) TfwStr *ct, *end; RclCtVal *curr; @@ -358,8 +360,7 @@ rcl_http_ct_check(const TfwHttpReq *req) * and pollute the headers table. */ TFW_HTTP_FOR_EACH_RAW_HDR_FIELD(ct, end, req) { - if (tfw_str_eq_cstr(ct, "Content-Type", sizeof("Content-Type"), - TFW_STR_EQ_PREFIX_CASEI)) + if (tfw_str_eq_cstr(ct, _CT, _CTLEN, TFW_STR_EQ_PREFIX_CASEI)) break; } @@ -375,10 +376,14 @@ rcl_http_ct_check(const TfwHttpReq *req) * usually faster for small sets of values. Perhaps we should switch * between two if the performance is that critical here, but benchmarks * should be done to measure the impact. + * + * TODO: don't store field name in the TfwStr. Store only the header + * value, and thus get rid of the nasty tfw_str_eq_kv(). */ rcu_read_lock(); for (curr = rcu_dereference(rcl_http_ct_vals); curr->str; ++curr) { - if (tfw_str_eq_cstr(ct, curr->str, curr->len, TFW_STR_EQ_CASEI)) + if (tfw_str_eq_kv(ct, _CT, _CTLEN, ':', curr->str, curr->len, + TFW_STR_EQ_PREFIX_CASEI)) break; } rcu_read_unlock(); @@ -389,6 +394,8 @@ rcl_http_ct_check(const TfwHttpReq *req) } return TFW_PASS; +#undef _CT +#undef _CTLEN } static int From f6d4ec0b457df2f6bd111863f4fb78fe46a15743 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Thu, 26 Feb 2015 04:21:00 +0300 Subject: [PATCH 10/28] rcl: update the header comment --- tempesta_fw/classifier/req_conn_limit.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/req_conn_limit.c index 0d311332b..03b66e452 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/req_conn_limit.c @@ -1,16 +1,25 @@ /** * Tempesta FW * - * Simple classification module which performs following limitings per a client: + * Simple classification module which performs following limitings: + * + * Temporal limitings per a client: * 1. HTTP requests rate; * 2. number of concurrent connections; * 3. new connections rate. * All the limits works for specified temporal bursts. * + * Static limits for contents of a HTTP request: + * 1. maximum length of URI/header/body. + * 2. checks for presence of certain required header fields + * 3. HTTP method and Content-Type restrictions (check that the value is + * in a set of allowed values defined by the user). + * * The module exports appropriate configuration options in - * /proc/net/tempesta/req_conn_limit directory. Options with names *_rate - * define requests/connections rate per second. *_burst are temporal burst - * for 1/RCL_FREQ of second. + * /proc/net/tempesta/req_conn_limit directory. + * - options with names *_rate define requests/connections rate per second. + * - *_burst are temporal burst for 1/RCL_FREQ of second. + * - http_* are static limits for contents of a HTTP request. * * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). * Copyright (C) 2015 Tempesta Technologies, Inc. From f1c8599cf9e5b936c695fdef46e406acda4e2d88 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Thu, 26 Feb 2015 07:16:18 +0300 Subject: [PATCH 11/28] rcl: rename the tfw_req_conn_limit module to tfw_frang No logic is changed, only the re-naming is done. --- tempesta_fw/classifier/Makefile | 6 +- .../classifier/{req_conn_limit.c => frang.c} | 470 +++++++++--------- tempesta_fw/gfsm.h | 4 +- 3 files changed, 241 insertions(+), 239 deletions(-) rename tempesta_fw/classifier/{req_conn_limit.c => frang.c} (56%) diff --git a/tempesta_fw/classifier/Makefile b/tempesta_fw/classifier/Makefile index 2d8f4e832..201cff6e9 100644 --- a/tempesta_fw/classifier/Makefile +++ b/tempesta_fw/classifier/Makefile @@ -17,8 +17,8 @@ # Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXTRA_CFLAGS += -I$(src)/../../sync_socket -I$(src)/../../tempesta_db/core \ - -DDEBUG + -DDEBUG -Werror -obj-m = tfw_req_conn_limit.o -tfw_req_conn_limit-objs = req_conn_limit.o +obj-m = tfw_frang.o +tfw_frang-objs = frang.o diff --git a/tempesta_fw/classifier/req_conn_limit.c b/tempesta_fw/classifier/frang.c similarity index 56% rename from tempesta_fw/classifier/req_conn_limit.c rename to tempesta_fw/classifier/frang.c index 03b66e452..c5854dc14 100644 --- a/tempesta_fw/classifier/req_conn_limit.c +++ b/tempesta_fw/classifier/frang.c @@ -16,9 +16,9 @@ * in a set of allowed values defined by the user). * * The module exports appropriate configuration options in - * /proc/net/tempesta/req_conn_limit directory. + * /proc/net/tempesta/frang directory. * - options with names *_rate define requests/connections rate per second. - * - *_burst are temporal burst for 1/RCL_FREQ of second. + * - *_burst are temporal burst for 1/FRANG_FREQ of second. * - http_* are static limits for contents of a HTTP request. * * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). @@ -52,70 +52,70 @@ #include "../log.h" MODULE_AUTHOR(TFW_AUTHOR); -MODULE_DESCRIPTION("Tempesta rate limiting classifier"); +MODULE_DESCRIPTION("Tempesta static limiting classifier"); MODULE_VERSION("0.1.1"); MODULE_LICENSE("GPL"); -/* We account users with RCL_FREQ frequency per second. */ -#define RCL_FREQ 8 +/* We account users with FRANG_FREQ frequency per second. */ +#define FRANG_FREQ 8 /* Garbage collection timeout (seconds). */ #define GC_TO 1800 -#define RCL_HASH_BITS 17 +#define FRANG_HASH_BITS 17 typedef struct { unsigned long ts; unsigned int conn_new; unsigned int req; -} RclRates; +} FrangRates; -typedef struct rcl_account_t { +typedef struct frang_account_t { struct hlist_node hentry; struct in6_addr addr; /* client address */ unsigned long last_ts; /* last access time */ unsigned int conn_curr; /* current connections number */ - RclRates history[RCL_FREQ]; -} RclAccount; + FrangRates history[FRANG_FREQ]; +} FrangAcc; typedef struct { struct hlist_head list; spinlock_t lock; -} RclHashBucket; +} FrangHashBucket; -RclHashBucket rcl_hash[1 << RCL_HASH_BITS] = { - [0 ... ((1 << RCL_HASH_BITS) - 1)] = { +FrangHashBucket frang_hash[1 << FRANG_HASH_BITS] = { + [0 ... ((1 << FRANG_HASH_BITS) - 1)] = { HLIST_HEAD_INIT, __SPIN_LOCK_UNLOCKED(lock) } }; -static struct kmem_cache *rcl_mem_cache; +static struct kmem_cache *frang_mem_cache; /* Limits (zero means unlimited). */ -static unsigned int rcl_req_rate = 0; -static unsigned int rcl_req_burst = 0; -static unsigned int rcl_conn_rate = 0; -static unsigned int rcl_conn_burst = 0; -static unsigned int rcl_conn_max = 0; +static unsigned int frang_req_rate = 0; +static unsigned int frang_req_burst = 0; +static unsigned int frang_conn_rate = 0; +static unsigned int frang_conn_burst = 0; +static unsigned int frang_conn_max = 0; /* Limits for HTTP request contents: uri, headers, body, etc. */ -static unsigned int rcl_http_uri_len = 0; -static unsigned int rcl_http_field_len = 0; -static unsigned int rcl_http_body_len = 0; -static unsigned long rcl_http_methods_mask = 0; -static bool rcl_http_ct_is_required = false; -static bool rcl_http_host_is_required = false; +static unsigned int frang_http_uri_len = 0; +static unsigned int frang_http_field_len = 0; +static unsigned int frang_http_body_len = 0; +static unsigned long frang_http_methods_mask = 0; +static bool frang_http_ct_is_required = false; +static bool frang_http_host_is_required = false; /* The list of allowed Content-Type values. */ typedef struct { char *str; size_t len; /* The pre-computed strlen(@str). */ -} RclCtVal; +} FrangCtVal; -static RclCtVal *rcl_http_ct_vals __rcu; +static FrangCtVal *frang_http_ct_vals __rcu; static void -rcl_get_ipv6addr(struct sock *sk, struct in6_addr *addr) +frang_get_ipv6addr(struct sock *sk, struct in6_addr *addr) { struct inet_sock *isk = (struct inet_sock *)sk; @@ -128,19 +128,19 @@ rcl_get_ipv6addr(struct sock *sk, struct in6_addr *addr) } static int -rcl_account_do(struct sock *sk, int (*func)(RclAccount *ra, struct sock *sk)) +frang_account_do(struct sock *sk, int (*func)(FrangAcc *ra, struct sock *sk)) { struct in6_addr addr; struct hlist_node *tmp; - RclAccount *ra; - RclHashBucket *hb; + FrangAcc *ra; + FrangHashBucket *hb; unsigned int key, r; - rcl_get_ipv6addr(sk, &addr); + frang_get_ipv6addr(sk, &addr); key = addr.s6_addr32[0] ^ addr.s6_addr32[1] ^ addr.s6_addr32[2] ^ addr.s6_addr32[3]; - hb = &rcl_hash[hash_min(key, RCL_HASH_BITS)]; + hb = &frang_hash[hash_min(key, FRANG_HASH_BITS)]; spin_lock(&hb->lock); @@ -159,9 +159,9 @@ rcl_account_do(struct sock *sk, int (*func)(RclAccount *ra, struct sock *sk)) * Other CPUs should not add the same account while we * allocating the account w/o lock. */ - ra = kmem_cache_alloc(rcl_mem_cache, GFP_ATOMIC | __GFP_ZERO); + ra = kmem_cache_alloc(frang_mem_cache, GFP_ATOMIC | __GFP_ZERO); if (!ra) { - TFW_WARN("rcl: can't alloc account record\n"); + TFW_WARN("frang: can't alloc account record\n"); return TFW_BLOCK; } @@ -181,11 +181,11 @@ rcl_account_do(struct sock *sk, int (*func)(RclAccount *ra, struct sock *sk)) } static int -rcl_conn_limit(RclAccount *ra, struct sock *unused) +frang_conn_limit(FrangAcc *ra, struct sock *unused) { - unsigned long ts = jiffies * RCL_FREQ / HZ; + unsigned long ts = jiffies * FRANG_FREQ / HZ; unsigned int csum = 0; - int i = ts % RCL_FREQ; + int i = ts % FRANG_FREQ; if (ra->history[i].ts != ts) { ra->history[i].ts = ts; @@ -196,36 +196,36 @@ rcl_conn_limit(RclAccount *ra, struct sock *unused) /* * Increment connection counters ever if we return TFW_BLOCK. * Synchronous sockets will call connection_drop callback, - * so our rcl_conn_close() is also called and we decrement + * so our frang_conn_close() is also called and we decrement * conn_curr there, but leave conn_new as is - we account failed * connection tries as well as successfully establised connections. */ ra->history[i].conn_new++; ra->conn_curr++; - if (rcl_conn_max && ra->conn_curr > rcl_conn_max) + if (frang_conn_max && ra->conn_curr > frang_conn_max) return TFW_BLOCK; - if (rcl_req_burst && ra->history[i].req > rcl_req_burst) + if (frang_req_burst && ra->history[i].req > frang_req_burst) return TFW_BLOCK; /* Collect new connections sum. */ - for (i = 0; i < RCL_FREQ; i++) - if (ra->history[i].ts + RCL_FREQ >= ts) + for (i = 0; i < FRANG_FREQ; i++) + if (ra->history[i].ts + FRANG_FREQ >= ts) csum += ra->history[i].conn_new; - if (rcl_conn_rate && csum > rcl_conn_rate) + if (frang_conn_rate && csum > frang_conn_rate) return TFW_BLOCK; return TFW_PASS; } static int -rcl_conn_new(struct sock *sk) +frang_conn_new(struct sock *sk) { - return rcl_account_do(sk, rcl_conn_limit); + return frang_account_do(sk, frang_conn_limit); } static int -__rcl_conn_close(RclAccount *ra, struct sock *unused) +__frang_conn_close(FrangAcc *ra, struct sock *unused) { BUG_ON(!ra->conn_curr); @@ -238,17 +238,17 @@ __rcl_conn_close(RclAccount *ra, struct sock *unused) * Just update current connection count for the user. */ static int -rcl_conn_close(struct sock *sk) +frang_conn_close(struct sock *sk) { - return rcl_account_do(sk, __rcl_conn_close); + return frang_account_do(sk, __frang_conn_close); } static int -rcl_req_limit(RclAccount *ra, struct sock *sk) +frang_req_limit(FrangAcc *ra, struct sock *sk) { - unsigned long ts = jiffies * RCL_FREQ / HZ; + unsigned long ts = jiffies * FRANG_FREQ / HZ; unsigned int rsum = 0; - int i = ts % RCL_FREQ; + int i = ts % FRANG_FREQ; if (ra->history[i].ts != ts) { ra->history[i].ts = ts; @@ -257,14 +257,14 @@ rcl_req_limit(RclAccount *ra, struct sock *sk) } ra->history[i].req++; - if (rcl_req_burst && ra->history[i].req > rcl_req_burst) + if (frang_req_burst && ra->history[i].req > frang_req_burst) goto block; /* Collect current request sum. */ - for (i = 0; i < RCL_FREQ; i++) - if (ra->history[i].ts + RCL_FREQ >= ts) + for (i = 0; i < FRANG_FREQ; i++) + if (ra->history[i].ts + FRANG_FREQ >= ts) rsum += ra->history[i].req; - if (rcl_req_rate && rsum > rcl_req_rate) + if (frang_req_rate && rsum > frang_req_rate) goto block; return TFW_PASS; @@ -280,12 +280,12 @@ rcl_req_limit(RclAccount *ra, struct sock *sk) } static int -rcl_http_uri_len_limit(const TfwHttpReq *req) +frang_http_uri_len_limit(const TfwHttpReq *req) { /* FIXME: tfw_str_len() iterates over chunks to calculate the length. * This is too slow. The value must be stored in a TfwStr field. */ - if (rcl_http_uri_len && tfw_str_len(&req->uri) > rcl_http_uri_len) { - TFW_DBG("rcl: http_uri_len limit is reached\n"); + if (frang_http_uri_len && tfw_str_len(&req->uri) > frang_http_uri_len) { + TFW_DBG("frang: http_uri_len limit is reached\n"); return TFW_BLOCK; } @@ -293,16 +293,16 @@ rcl_http_uri_len_limit(const TfwHttpReq *req) } static int -rcl_http_field_len_limit(const TfwHttpReq *req) +frang_http_field_len_limit(const TfwHttpReq *req) { const TfwStr *field, *end; - if (!rcl_http_field_len) + if (!frang_http_field_len) return TFW_PASS; TFW_HTTP_FOR_EACH_HDR_FIELD(field, end, req) { - if (tfw_str_len(field) > rcl_http_field_len) { - TFW_DBG("rcl: http_field_len limit is reached\n"); + if (tfw_str_len(field) > frang_http_field_len) { + TFW_DBG("frang: http_field_len limit is reached\n"); return TFW_BLOCK; } } @@ -311,10 +311,11 @@ rcl_http_field_len_limit(const TfwHttpReq *req) } static int -rcl_http_body_len_limit(const TfwHttpReq *req) +frang_http_body_len_limit(const TfwHttpReq *req) { - if (rcl_http_body_len && tfw_str_len(&req->body) > rcl_http_body_len) { - TFW_DBG("rcl: http_body_len limit is reached\n"); + if (frang_http_body_len && + tfw_str_len(&req->body) > frang_http_body_len) { + TFW_DBG("frang: http_body_len limit is reached\n"); return TFW_BLOCK; } @@ -322,17 +323,17 @@ rcl_http_body_len_limit(const TfwHttpReq *req) } static int -rcl_http_len_limit(const TfwHttpReq *req) +frang_http_len_limit(const TfwHttpReq *req) { int r; - r = rcl_http_uri_len_limit(req); + r = frang_http_uri_len_limit(req); if (r) return r; - r = rcl_http_field_len_limit(req); + r = frang_http_field_len_limit(req); if (r) return r; - r = rcl_http_body_len_limit(req); + r = frang_http_body_len_limit(req); if (r) return r; @@ -340,26 +341,26 @@ rcl_http_len_limit(const TfwHttpReq *req) } static int -rcl_http_methods_check(const TfwHttpReq *req) +frang_http_methods_check(const TfwHttpReq *req) { unsigned long m = (1 << req->method); - if (rcl_http_methods_mask && (rcl_http_methods_mask & m)) + if (frang_http_methods_mask && (frang_http_methods_mask & m)) return TFW_PASS; - TFW_DBG("rcl: forbidden method: %d (%#lx)\n", req->method, m); + TFW_DBG("frang: forbidden method: %d (%#lx)\n", req->method, m); return TFW_BLOCK; } static int -rcl_http_ct_check(const TfwHttpReq *req) +frang_http_ct_check(const TfwHttpReq *req) { #define _CT "Content-Type" #define _CTLEN (sizeof(_CT) - 1) TfwStr *ct, *end; - RclCtVal *curr; + FrangCtVal *curr; - if (!rcl_http_ct_is_required || req->method != TFW_HTTP_METH_POST) + if (!frang_http_ct_is_required || req->method != TFW_HTTP_METH_POST) return TFW_PASS; /* Find the Content-Type header. @@ -374,7 +375,7 @@ rcl_http_ct_check(const TfwHttpReq *req) } if (ct == end) { - TFW_DBG("rcl: Content-Type is missing\n"); + TFW_DBG("frang: Content-Type is missing\n"); return TFW_BLOCK; } @@ -390,7 +391,7 @@ rcl_http_ct_check(const TfwHttpReq *req) * value, and thus get rid of the nasty tfw_str_eq_kv(). */ rcu_read_lock(); - for (curr = rcu_dereference(rcl_http_ct_vals); curr->str; ++curr) { + for (curr = rcu_dereference(frang_http_ct_vals); curr->str; ++curr) { if (tfw_str_eq_kv(ct, _CT, _CTLEN, ':', curr->str, curr->len, TFW_STR_EQ_PREFIX_CASEI)) break; @@ -398,7 +399,7 @@ rcl_http_ct_check(const TfwHttpReq *req) rcu_read_unlock(); if (!curr->str) { - TFW_DBG("rcl: forbidden Content-Type value\n"); + TFW_DBG("frang: forbidden Content-Type value\n"); return TFW_BLOCK; } @@ -408,16 +409,16 @@ rcl_http_ct_check(const TfwHttpReq *req) } static int -rcl_http_host_check(const TfwHttpReq *req) +frang_http_host_check(const TfwHttpReq *req) { TfwStr *field; - if (!rcl_http_host_is_required || req->method != TFW_HTTP_METH_POST) + if (!frang_http_host_is_required || req->method != TFW_HTTP_METH_POST) return TFW_PASS; field = &req->h_tbl->tbl[TFW_HTTP_HDR_HOST].field; if (!field->ptr) { - TFW_DBG("rcl: the Host header is missing\n"); + TFW_DBG("frang: the Host header is missing\n"); return TFW_BLOCK; } @@ -429,22 +430,22 @@ rcl_http_host_check(const TfwHttpReq *req) } static int -rcl_http_req_handler(void *obj, unsigned char *data, size_t len) +frang_http_req_handler(void *obj, unsigned char *data, size_t len) { int r; TfwConnection *c = (TfwConnection *)obj; TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); - r = rcl_account_do(c->sess->cli->sock, rcl_req_limit); + r = frang_account_do(c->sess->cli->sock, frang_req_limit); if (r) return r; - r = rcl_http_len_limit(req); + r = frang_http_len_limit(req); if (r) return r; - r = rcl_http_ct_check(req); + r = frang_http_ct_check(req); if (r) return r; - r = rcl_http_host_check(req); + r = frang_http_host_check(req); if (r) return r; @@ -452,29 +453,29 @@ rcl_http_req_handler(void *obj, unsigned char *data, size_t len) } static int -rcl_http_chunk_handler(void *obj, unsigned char *data, size_t len) +frang_http_chunk_handler(void *obj, unsigned char *data, size_t len) { int r; TfwConnection *c = (TfwConnection *)obj; TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); - r = rcl_http_methods_check(req); + r = frang_http_methods_check(req); if (r) return r; - r = rcl_http_len_limit(req); + r = frang_http_len_limit(req); if (r) return r; return 0; } -static TfwClassifier rcl_class_ops = { - .classify_conn_estab = rcl_conn_new, - .classify_conn_close = rcl_conn_close, +static TfwClassifier frang_class_ops = { + .classify_conn_estab = frang_conn_new, + .classify_conn_close = frang_conn_close, }; static int -rcl_parse_int(char *tmp_buf, int *out_val) +frang_parse_int(char *tmp_buf, int *out_val) { char *p; int val = 0; @@ -492,7 +493,7 @@ rcl_parse_int(char *tmp_buf, int *out_val) } static int -rcl_parse_bool(char *tmp_buf, bool *out_bool) +frang_parse_bool(char *tmp_buf, bool *out_bool) { char c = *tmp_buf; @@ -510,12 +511,12 @@ rcl_parse_bool(char *tmp_buf, bool *out_bool) * replace them with Tempesta's cfg framework. */ static int -rcl_find_idx_by_str(const char **str_vec, size_t str_vec_size, const char *str) +frang_find_idx_by_str(const char **str_vec, size_t vec_size, const char *str) { int i; const char *curr_str; - for (i = 0; i < str_vec_size; ++i) { + for (i = 0; i < vec_size; ++i) { curr_str = str_vec[i]; BUG_ON(!curr_str); if (!strcasecmp(curr_str, str)) @@ -525,12 +526,12 @@ rcl_find_idx_by_str(const char **str_vec, size_t str_vec_size, const char *str) return -1; } -#define RCL_TOKEN_SEPARATORS " \r\n\t" +#define FRANG_TOKEN_SEPARATORS " \r\n\t" static char * -rcl_tokenize(char **tmp_buf) +frang_tokenize(char **tmp_buf) { - char *token = strsep(tmp_buf, RCL_TOKEN_SEPARATORS); + char *token = strsep(tmp_buf, FRANG_TOKEN_SEPARATORS); /* Unlike strsep(), don't return empty tokens. */ if (token && !*token) @@ -540,22 +541,22 @@ rcl_tokenize(char **tmp_buf) } static int -rcl_count_tokens(char *str) +frang_count_tokens(char *str) { int n = 0; while (*str) { - str += strspn(str, RCL_TOKEN_SEPARATORS); + str += strspn(str, FRANG_TOKEN_SEPARATORS); if (*str) ++n; - str += strcspn(str, RCL_TOKEN_SEPARATORS); + str += strcspn(str, FRANG_TOKEN_SEPARATORS); } return n; } static int -rcl_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) +frang_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) { static const char *strs[_TFW_HTTP_METH_COUNT] = { [TFW_HTTP_METH_GET] = "get", @@ -563,20 +564,20 @@ rcl_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) [TFW_HTTP_METH_HEAD] = "head", }; char *token; - int method_idx; + int idx; unsigned long methods_mask; token = tmp_buf; methods_mask = 0; - while ((token = rcl_tokenize(&tmp_buf))) { - method_idx = rcl_find_idx_by_str(strs, ARRAY_SIZE(strs), token); - if (method_idx < 0) { - TFW_ERR("rcl: invalid method: '%s'\n", token); + while ((token = frang_tokenize(&tmp_buf))) { + idx = frang_find_idx_by_str(strs, ARRAY_SIZE(strs), token); + if (idx < 0) { + TFW_ERR("frang: invalid method: '%s'\n", token); return -EINVAL; } - TFW_DBG("rcl: parsed method: %s => %d\n", token, method_idx); - methods_mask |= (1 << method_idx); + TFW_DBG("frang: parsed method: %s => %d\n", token, idx); + methods_mask |= (1 << idx); } TFW_DBG("parsed methods_mask: %#lx\n", methods_mask); @@ -585,16 +586,16 @@ rcl_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) } static int -rcl_parse_ct_vals(char *tmp_buf, void *unused) +frang_parse_ct_vals(char *tmp_buf, void *unused) { void *mem; char *token, *strs, *strs_pos; size_t tokens_n, vals_size, strs_size; - RclCtVal *vals, *vals_pos, *old_vals; + FrangCtVal *vals, *vals_pos, *old_vals; - tokens_n = rcl_count_tokens(tmp_buf); + tokens_n = frang_count_tokens(tmp_buf); if (!tokens_n) { - TFW_ERR("the rcl_http_ct_vals is empty\n"); + TFW_ERR("the frang_http_ct_vals is empty\n"); return -EINVAL; } @@ -602,17 +603,17 @@ rcl_parse_ct_vals(char *tmp_buf, void *unused) * variable-sized list of variable-sized strings. * * Basically that will look like: - * [[RclCtVal, RclCtVal, RclCtVal, NULL]string1\0\string2\0\string3\0] - * + + + ^ ^ ^ - * | | | | | | - * +---------------------------+ | | - * | | | | - * +---------------------------+ | + * [[FrangCtVal, FrangCtVal, FrangCtVal, NULL]str1\0\str2\0\str3\0] + * + + + ^ ^ ^ + * | | | | | | + * +---------------------------------+ | | + * | | | | + * +------------------------------+ | * | | * +---------------------------+ */ strs_size = strlen(tmp_buf) + 1; - vals_size = sizeof(RclCtVal) * (tokens_n + 1); + vals_size = sizeof(FrangCtVal) * (tokens_n + 1); mem = kzalloc(vals_size + strs_size, GFP_KERNEL); vals = mem; strs = mem + vals_size; @@ -621,7 +622,7 @@ rcl_parse_ct_vals(char *tmp_buf, void *unused) /* TODO: validate tokens, they should look like: "text/plain". */ vals_pos = vals; strs_pos = strs; - while ((token = rcl_tokenize(&tmp_buf))) { + while ((token = frang_tokenize(&tmp_buf))) { size_t len = strlen(token) + 1; BUG_ON(!len); @@ -638,15 +639,15 @@ rcl_parse_ct_vals(char *tmp_buf, void *unused) /* Replace the old list of allowed Content-Type values. */ /* TODO: sort values to make binary search possible. */ - old_vals = rcl_http_ct_vals; - rcu_assign_pointer(rcl_http_ct_vals, vals); + old_vals = frang_http_ct_vals; + rcu_assign_pointer(frang_http_ct_vals, vals); synchronize_rcu(); kfree(old_vals); return 0; } static int -rcl_sysctl_handle(ctl_table *ctl, int write, +frang_sysctl_handle(ctl_table *ctl, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { int r, len; @@ -662,21 +663,21 @@ rcl_sysctl_handle(ctl_table *ctl, int write, if (write) { tmp_buf = kzalloc(ctl->maxlen + 1, GFP_KERNEL); if (!tmp_buf) { - TFW_ERR("rcl: can't allocate temporary buffer\n"); + TFW_ERR("frang: can't allocate temporary buffer\n"); r = -ENOMEM; goto out; } len = min(ctl->maxlen, (int)*lenp); if (copy_from_user(tmp_buf, buffer, len)) { - TFW_ERR("rcl: can't copy data from userspace\n"); + TFW_ERR("frang: can't copy data from userspace\n"); r = -EFAULT; goto out; } trimmed_buf = strim(tmp_buf); if (parse_fn(trimmed_buf, parse_dest)) { - TFW_ERR("rcl: can't parse input data\n"); + TFW_ERR("frang: can't parse input data\n"); r = -EINVAL; goto out; } @@ -684,138 +685,139 @@ rcl_sysctl_handle(ctl_table *ctl, int write, r = proc_dostring(ctl, write, buffer, lenp, ppos); if (r) - TFW_ERR("rcl: sysctl error\n"); + TFW_ERR("frang: sysctl error\n"); out: if (r) - TFW_ERR("rcl: can't read/write parameter: %s\n", ctl->procname); + TFW_ERR("frang: can't read/write parameter: %s\n", + ctl->procname); kfree(tmp_buf); return r; } -#define RCL_INT_LEN 10 -#define RCL_STR_LEN 255 -#define RCL_LONG_STR_LEN 1024 - -char rcl_req_rate_str[RCL_INT_LEN]; -char rcl_req_burst_str[RCL_INT_LEN]; -char rcl_conn_rate_str[RCL_INT_LEN]; -char rcl_conn_burst_str[RCL_INT_LEN]; -char rcl_conn_max_str[RCL_INT_LEN]; -char rcl_http_uri_len_str[RCL_INT_LEN]; -char rcl_http_field_len_str[RCL_INT_LEN]; -char rcl_http_body_len_str[RCL_INT_LEN]; -char rcl_http_methods_str[RCL_STR_LEN]; -char rcl_http_ct_is_required_str[RCL_INT_LEN]; -char rcl_http_content_types_str[RCL_LONG_STR_LEN]; -char rcl_http_host_is_required_str[RCL_INT_LEN]; - -static ctl_table rcl_ctl_table[] = { +#define FRANG_INT_LEN 10 +#define FRANG_STR_LEN 255 +#define FRANG_LONG_STR_LEN 1024 + +char frang_req_rate_str[FRANG_INT_LEN]; +char frang_req_burst_str[FRANG_INT_LEN]; +char frang_conn_rate_str[FRANG_INT_LEN]; +char frang_conn_burst_str[FRANG_INT_LEN]; +char frang_conn_max_str[FRANG_INT_LEN]; +char frang_http_uri_len_str[FRANG_INT_LEN]; +char frang_http_field_len_str[FRANG_INT_LEN]; +char frang_http_body_len_str[FRANG_INT_LEN]; +char frang_http_methods_str[FRANG_STR_LEN]; +char frang_http_ct_is_required_str[FRANG_INT_LEN]; +char frang_http_content_types_str[FRANG_LONG_STR_LEN]; +char frang_http_host_is_required_str[FRANG_INT_LEN]; + +static ctl_table frang_ctl_table[] = { { .procname = "request_rate", - .data = rcl_req_rate_str, - .maxlen = sizeof(rcl_req_rate_str), + .data = frang_req_rate_str, + .maxlen = sizeof(frang_req_rate_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_req_rate, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_req_rate, }, { .procname = "request_burst", - .data = rcl_req_burst_str, - .maxlen = sizeof(rcl_req_burst_str), + .data = frang_req_burst_str, + .maxlen = sizeof(frang_req_burst_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_req_burst, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_req_burst, }, { .procname = "new_connection_rate", - .data = rcl_conn_rate_str, - .maxlen = sizeof(rcl_conn_rate_str), + .data = frang_conn_rate_str, + .maxlen = sizeof(frang_conn_rate_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_conn_rate, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_conn_rate, }, { .procname = "new_connection_burst", - .data = rcl_conn_burst_str, - .maxlen = sizeof(rcl_conn_burst_str), + .data = frang_conn_burst_str, + .maxlen = sizeof(frang_conn_burst_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_conn_burst, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_conn_burst, }, { .procname = "concurrent_connections", - .data = rcl_conn_max_str, - .maxlen = sizeof(rcl_conn_max_str), + .data = frang_conn_max_str, + .maxlen = sizeof(frang_conn_max_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_conn_max, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_conn_max, }, { .procname = "http_uri_len", - .data = rcl_http_uri_len_str, - .maxlen = sizeof(rcl_http_uri_len_str), + .data = frang_http_uri_len_str, + .maxlen = sizeof(frang_http_uri_len_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_http_uri_len, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_http_uri_len, }, { .procname = "http_field_len", - .data = rcl_http_field_len_str, - .maxlen = sizeof(rcl_http_field_len_str), + .data = frang_http_field_len_str, + .maxlen = sizeof(frang_http_field_len_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_http_field_len, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_http_field_len, }, { .procname = "http_body_len", - .data = rcl_http_body_len_str, - .maxlen = sizeof(rcl_http_body_len_str), + .data = frang_http_body_len_str, + .maxlen = sizeof(frang_http_body_len_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_int, - .extra2 = &rcl_http_body_len, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_int, + .extra2 = &frang_http_body_len, }, { .procname = "http_methods", - .data = rcl_http_methods_str, - .maxlen = sizeof(rcl_http_methods_str), + .data = frang_http_methods_str, + .maxlen = sizeof(frang_http_methods_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_methods_mask, - .extra2 = &rcl_http_methods_mask, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_methods_mask, + .extra2 = &frang_http_methods_mask, }, { .procname = "http_ct_is_required", - .data = rcl_http_ct_is_required_str, - .maxlen = sizeof(rcl_http_ct_is_required_str), + .data = frang_http_ct_is_required_str, + .maxlen = sizeof(frang_http_ct_is_required_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_bool, - .extra2 = &rcl_http_ct_is_required, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_bool, + .extra2 = &frang_http_ct_is_required, }, { .procname = "http_ct_vals", - .data = rcl_http_content_types_str, - .maxlen = sizeof(rcl_http_content_types_str), + .data = frang_http_content_types_str, + .maxlen = sizeof(frang_http_content_types_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_ct_vals, + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_ct_vals, }, { .procname = "http_host_is_required", - .data = rcl_http_host_is_required_str, - .maxlen = sizeof(rcl_http_host_is_required_str), + .data = frang_http_host_is_required_str, + .maxlen = sizeof(frang_http_host_is_required_str), .mode = 0644, - .proc_handler = rcl_sysctl_handle, - .extra1 = rcl_parse_bool, - .extra2 = &rcl_http_host_is_required + .proc_handler = frang_sysctl_handle, + .extra1 = frang_parse_bool, + .extra2 = &frang_http_host_is_required }, {} }; @@ -825,28 +827,28 @@ static struct ctl_path __tfw_path[] = { }; static int __init -rcl_init(void) +frang_init(void) { int r; - struct ctl_table_header *rcl_ctl; + struct ctl_table_header *frang_ctl; - rcl_mem_cache = KMEM_CACHE(rcl_account_t, 0); - if (!rcl_mem_cache) { - TFW_ERR("rcl: can't create cache\n"); + frang_mem_cache = KMEM_CACHE(frang_account_t, 0); + if (!frang_mem_cache) { + TFW_ERR("frang: can't create cache\n"); return -EINVAL; } - rcl_ctl = register_net_sysctl(&init_net, "net/tempesta/req_conn_limit", - rcl_ctl_table); - if (!rcl_ctl) { - TFW_ERR("rcl: can't register sysctl table\n"); + frang_ctl = register_net_sysctl(&init_net, "net/tempesta/frang", + frang_ctl_table); + if (!frang_ctl) { + TFW_ERR("frang: can't register sysctl table\n"); r = -1; goto err_sysctl; } - r = tfw_classifier_register(&rcl_class_ops); + r = tfw_classifier_register(&frang_class_ops); if (r) { - TFW_ERR("rcl: can't register classifier\n"); + TFW_ERR("frang: can't register classifier\n"); goto err_class; } @@ -870,48 +872,48 @@ rcl_init(void) * This is easy to comprehend, and perhaps faster since the GFSM * doesn't need to switch FSMs. */ - r = tfw_gfsm_register_fsm(TFW_FSM_RCL_REQ, rcl_http_req_handler); + r = tfw_gfsm_register_fsm(TFW_FSM_FRANG_REQ, frang_http_req_handler); if (r) { - TFW_ERR("rcl: can't register fsm: req\n"); + TFW_ERR("frang: can't register fsm: req\n"); goto err_fsm_req; } - r = tfw_gfsm_register_fsm(TFW_FSM_RCL_CHUNK, rcl_http_chunk_handler); + r = tfw_gfsm_register_fsm(TFW_FSM_FRANG_CHUNK, frang_http_chunk_handler); if (r) { - TFW_ERR("rcl: can't register fsm: chunk\n"); + TFW_ERR("frang: can't register fsm: chunk\n"); goto err_fsm_chunk; } r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, TFW_HTTP_FSM_REQ_MSG, 0, - TFW_FSM_RCL_REQ); + TFW_FSM_FRANG_REQ); if (r) { - TFW_ERR("rcl: can't register gfsm hook: req\n"); + TFW_ERR("frang: can't register gfsm hook: req\n"); goto err_hook_req; } r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, TFW_HTTP_FSM_REQ_CHUNK, 0, - TFW_FSM_RCL_CHUNK); + TFW_FSM_FRANG_CHUNK); if (r) { - TFW_ERR("rcl: can't register gfsm hook: chunk\n"); - TFW_ERR("rcl: can't recover\n"); + TFW_ERR("frang: can't register gfsm hook: chunk\n"); + TFW_ERR("frang: can't recover\n"); BUG(); } - TFW_WARN("rcl mudule can't be unloaded, " + TFW_WARN("frang mudule can't be unloaded, " "so all allocated resources won't freed\n"); return 0; err_hook_req: - tfw_gfsm_unregister_fsm(TFW_FSM_RCL_CHUNK); + tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_CHUNK); err_fsm_chunk: - tfw_gfsm_unregister_fsm(TFW_FSM_RCL_REQ); + tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_REQ); err_fsm_req: tfw_classifier_unregister(); err_class: - unregister_sysctl_table(rcl_ctl); + unregister_sysctl_table(frang_ctl); err_sysctl: - kmem_cache_destroy(rcl_mem_cache); + kmem_cache_destroy(frang_mem_cache); return r; } -module_init(rcl_init); +module_init(frang_init); diff --git a/tempesta_fw/gfsm.h b/tempesta_fw/gfsm.h index 7d65e03bf..e42dbd1ae 100644 --- a/tempesta_fw/gfsm.h +++ b/tempesta_fw/gfsm.h @@ -81,8 +81,8 @@ enum { TFW_FSM_HTTPS, /* Request connection limiting classifier */ - TFW_FSM_RCL_REQ, - TFW_FSM_RCL_CHUNK, + TFW_FSM_FRANG_REQ, + TFW_FSM_FRANG_CHUNK, TFW_FSM_NUM /* Must be <= TFW_GFSM_FSM_N */ }; From a5e04de9d847d1ef6a13320e610cd1c74e0fbc6f Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 23 Mar 2015 11:54:32 +0300 Subject: [PATCH 12/28] frang: remove a bit of garbage left after the rebase --- tempesta_fw/classifier/frang.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index c5854dc14..2bc6c0262 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -821,10 +821,6 @@ static ctl_table frang_ctl_table[] = { }, {} }; -static struct ctl_path __tfw_path[] = { - { .procname = "net/tempesta/req_conn_limit", }, - {} -}; static int __init frang_init(void) From 2ac54a30ad07ded7ecde0afe3aec530925c30a6e Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 23 Mar 2015 14:22:04 +0300 Subject: [PATCH 13/28] frang: get rid of TFW_FSM_FRANG_CHUNK The implementation is completely wrong. It is done via a static hook that performs all checks for the whole HTTP request instead of checking only the latest chunk. The classifier should implement a FSM that moves in parallel with the HTTP parser FSM and checks only what was parsed in the latest chunk. We are not going to implement the FSM now, so this commit just leaves the TFW_FSM_FRANG hook which is executed for the whole HTTP request, but unlike TFW_FSM_FRANG_CHUNK, it doesnt do duplicate checks on each received chunk. --- tempesta_fw/classifier/frang.c | 74 +++++++--------------------------- tempesta_fw/gfsm.h | 3 +- tempesta_fw/http.h | 2 +- 3 files changed, 17 insertions(+), 62 deletions(-) diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 2bc6c0262..c3dc1a336 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -437,6 +437,10 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); r = frang_account_do(c->sess->cli->sock, frang_req_limit); + if (r) + return r; + + r = frang_http_methods_check(req); if (r) return r; r = frang_http_len_limit(req); @@ -452,23 +456,6 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) return 0; } -static int -frang_http_chunk_handler(void *obj, unsigned char *data, size_t len) -{ - int r; - TfwConnection *c = (TfwConnection *)obj; - TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); - - r = frang_http_methods_check(req); - if (r) - return r; - r = frang_http_len_limit(req); - if (r) - return r; - - return 0; -} - static TfwClassifier frang_class_ops = { .classify_conn_estab = frang_conn_new, .classify_conn_close = frang_conn_close, @@ -848,62 +835,31 @@ frang_init(void) goto err_class; } - /** - * FIXME: - * Here we add two primitive hooks by registering two FSMs. - * There is a bunch of problems here: - * - We can't unregister hooks. Therefore, we can't unload this module - * and can't recover if the second tfw_gfsm_register_hook() fails. - * - We have to add every hook to the global enum of FSMs. - * - We register two FSMs, but actually don't implement anything close - * to FSM and don't need the FSM switching logic. - * - * The suggested solution is to extend the GFSM with the support of - * "lightweight hooks" that behave like plain functions rather than FSM. - * That should solve all these problems listed above: - * - Such hook may be unregistered any time it is not executed. - * - The hook doesn't need an ID, so no need to maintain a global list - * of all known hooks in the GFSM code. - * - No need to create a dummy FSM just to call the hook. - * This is easy to comprehend, and perhaps faster since the GFSM - * doesn't need to switch FSMs. - */ - r = tfw_gfsm_register_fsm(TFW_FSM_FRANG_REQ, frang_http_req_handler); + /* FIXME: this is not a FSM here, but rather a set of static checks + * that are executed when a HTTP request is fully parsed. + * These checks should be executed during the parsing process in order + * to drop suspicious requests as early as possible. */ + r = tfw_gfsm_register_fsm(TFW_FSM_FRANG, frang_http_req_handler); if (r) { TFW_ERR("frang: can't register fsm: req\n"); - goto err_fsm_req; - } - r = tfw_gfsm_register_fsm(TFW_FSM_FRANG_CHUNK, frang_http_chunk_handler); - if (r) { - TFW_ERR("frang: can't register fsm: chunk\n"); - goto err_fsm_chunk; + goto err_fsm; } r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, TFW_HTTP_FSM_REQ_MSG, 0, - TFW_FSM_FRANG_REQ); + TFW_FSM_FRANG); if (r) { TFW_ERR("frang: can't register gfsm hook: req\n"); - goto err_hook_req; - } - r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_REQ_CHUNK, 0, - TFW_FSM_FRANG_CHUNK); - if (r) { - TFW_ERR("frang: can't register gfsm hook: chunk\n"); - TFW_ERR("frang: can't recover\n"); - BUG(); + goto err_hook; } TFW_WARN("frang mudule can't be unloaded, " "so all allocated resources won't freed\n"); return 0; -err_hook_req: - tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_CHUNK); -err_fsm_chunk: - tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_REQ); -err_fsm_req: +err_hook: + tfw_gfsm_unregister_fsm(TFW_FSM_FRANG); +err_fsm: tfw_classifier_unregister(); err_class: unregister_sysctl_table(frang_ctl); diff --git a/tempesta_fw/gfsm.h b/tempesta_fw/gfsm.h index e42dbd1ae..1f3e28665 100644 --- a/tempesta_fw/gfsm.h +++ b/tempesta_fw/gfsm.h @@ -81,8 +81,7 @@ enum { TFW_FSM_HTTPS, /* Request connection limiting classifier */ - TFW_FSM_FRANG_REQ, - TFW_FSM_FRANG_CHUNK, + TFW_FSM_FRANG, TFW_FSM_NUM /* Must be <= TFW_GFSM_FSM_N */ }; diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 8b3247450..7cfdaeaff 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -36,7 +36,7 @@ typedef enum { TFW_HTTP_METH_GET, TFW_HTTP_METH_HEAD, TFW_HTTP_METH_POST, - _TFW_HTTP_METH_COUNT, + _TFW_HTTP_METH_COUNT } tfw_http_meth_t; #define TFW_HTTP_CC_NO_CACHE 0x001 From f5a22706c593152ed9f5a76f3722a05932b5f99e Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 23 Mar 2015 04:39:28 +0300 Subject: [PATCH 14/28] frang: switch from sysctl to tempesta cfg framework --- etc/tfw_frang.conf | 45 ++++ tempesta_fw/classifier/frang.c | 466 +++++++++++---------------------- 2 files changed, 191 insertions(+), 320 deletions(-) create mode 100644 etc/tfw_frang.conf diff --git a/etc/tfw_frang.conf b/etc/tfw_frang.conf new file mode 100644 index 000000000..c3207fa1f --- /dev/null +++ b/etc/tfw_frang.conf @@ -0,0 +1,45 @@ +# +# Tempesta FW configuration: "frang" classifier. +# +# "include" directives are not supported yet, so paste the configuration to +# the main configuration file. + +# TAG: frang_limits +# +# The section containing static limits for the classifier. +# +# Syntax: +# frang_limits { +# request_rate NUM; +# request_burst NUM; +# new_connection_rate NUM; +# new_connection_burst NUM; +# concurrent_connections NUM; +# http_uri_len NUM; +# http_field_len NUM; +# http_body_len NUM; +# http_host_is_required true|false; +# http_methods [METHOD]...; +# http_ct_is_required true|false; +# http_ct_vals ["CONTENT_TYPE"]...; +# } +# +# - options with names *_rate define requests/connections rate per second. +# - *_burst are temporal burst for 1/FRANG_FREQ of second. +# - http_* are static limits for contents of a HTTP request. +# +# Example: +# frang_limits { +# request_rate 20; +# request_burst 15; +# new_connection_rate 8; +# concurrent_connections 8; +# http_uri_len 1024; +# http_field_len 256; +# http_ct_is_required false; +# http_methods get post head; +# http_ct_vals "text/plain" "text/html"; +# } +# +# Default: +# All limits are disabled (the values are set to zero/false/empty). diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index c3dc1a336..1243c0c26 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -15,12 +15,6 @@ * 3. HTTP method and Content-Type restrictions (check that the value is * in a set of allowed values defined by the user). * - * The module exports appropriate configuration options in - * /proc/net/tempesta/frang directory. - * - options with names *_rate define requests/connections rate per second. - * - *_burst are temporal burst for 1/FRANG_FREQ of second. - * - http_* are static limits for contents of a HTTP request. - * * Copyright (C) 2012-2014 NatSys Lab. (info@natsys-lab.com). * Copyright (C) 2015 Tempesta Technologies, Inc. * @@ -90,29 +84,32 @@ FrangHashBucket frang_hash[1 << FRANG_HASH_BITS] = { static struct kmem_cache *frang_mem_cache; -/* Limits (zero means unlimited). */ -static unsigned int frang_req_rate = 0; -static unsigned int frang_req_burst = 0; -static unsigned int frang_conn_rate = 0; -static unsigned int frang_conn_burst = 0; -static unsigned int frang_conn_max = 0; - -/* Limits for HTTP request contents: uri, headers, body, etc. */ -static unsigned int frang_http_uri_len = 0; -static unsigned int frang_http_field_len = 0; -static unsigned int frang_http_body_len = 0; -static unsigned long frang_http_methods_mask = 0; -static bool frang_http_ct_is_required = false; -static bool frang_http_host_is_required = false; - -/* The list of allowed Content-Type values. */ - typedef struct { char *str; size_t len; /* The pre-computed strlen(@str). */ } FrangCtVal; -static FrangCtVal *frang_http_ct_vals __rcu; +typedef struct { + /* Limits (zero means unlimited). */ + unsigned int req_rate; + unsigned int req_burst; + unsigned int conn_rate; + unsigned int conn_burst; + unsigned int conn_max; + + /* Limits for HTTP request contents: uri, headers, body, etc. */ + unsigned int http_uri_len; + unsigned int http_field_len; + unsigned int http_body_len; + bool http_ct_is_required; + bool http_host_is_required; + /* The bitmask of allowed HTTP Method values. */ + unsigned long http_methods_mask; + /* The list of allowed Content-Type values. */ + FrangCtVal *http_ct_vals; +} FrangCfg; + +static FrangCfg frang_cfg __read_mostly; static void frang_get_ipv6addr(struct sock *sk, struct in6_addr *addr) @@ -203,16 +200,16 @@ frang_conn_limit(FrangAcc *ra, struct sock *unused) ra->history[i].conn_new++; ra->conn_curr++; - if (frang_conn_max && ra->conn_curr > frang_conn_max) + if (frang_cfg.conn_max && ra->conn_curr > frang_cfg.conn_max) return TFW_BLOCK; - if (frang_req_burst && ra->history[i].req > frang_req_burst) + if (frang_cfg.req_burst && ra->history[i].req > frang_cfg.req_burst) return TFW_BLOCK; /* Collect new connections sum. */ for (i = 0; i < FRANG_FREQ; i++) if (ra->history[i].ts + FRANG_FREQ >= ts) csum += ra->history[i].conn_new; - if (frang_conn_rate && csum > frang_conn_rate) + if (frang_cfg.conn_rate && csum > frang_cfg.conn_rate) return TFW_BLOCK; return TFW_PASS; @@ -257,14 +254,14 @@ frang_req_limit(FrangAcc *ra, struct sock *sk) } ra->history[i].req++; - if (frang_req_burst && ra->history[i].req > frang_req_burst) + if (frang_cfg.req_burst && ra->history[i].req > frang_cfg.req_burst) goto block; /* Collect current request sum. */ for (i = 0; i < FRANG_FREQ; i++) if (ra->history[i].ts + FRANG_FREQ >= ts) rsum += ra->history[i].req; - if (frang_req_rate && rsum > frang_req_rate) + if (frang_cfg.req_rate && rsum > frang_cfg.req_rate) goto block; return TFW_PASS; @@ -284,7 +281,8 @@ frang_http_uri_len_limit(const TfwHttpReq *req) { /* FIXME: tfw_str_len() iterates over chunks to calculate the length. * This is too slow. The value must be stored in a TfwStr field. */ - if (frang_http_uri_len && tfw_str_len(&req->uri) > frang_http_uri_len) { + if (frang_cfg.http_uri_len && + tfw_str_len(&req->uri) > frang_cfg.http_uri_len) { TFW_DBG("frang: http_uri_len limit is reached\n"); return TFW_BLOCK; } @@ -297,11 +295,11 @@ frang_http_field_len_limit(const TfwHttpReq *req) { const TfwStr *field, *end; - if (!frang_http_field_len) + if (!frang_cfg.http_field_len) return TFW_PASS; TFW_HTTP_FOR_EACH_HDR_FIELD(field, end, req) { - if (tfw_str_len(field) > frang_http_field_len) { + if (tfw_str_len(field) > frang_cfg.http_field_len) { TFW_DBG("frang: http_field_len limit is reached\n"); return TFW_BLOCK; } @@ -313,8 +311,8 @@ frang_http_field_len_limit(const TfwHttpReq *req) static int frang_http_body_len_limit(const TfwHttpReq *req) { - if (frang_http_body_len && - tfw_str_len(&req->body) > frang_http_body_len) { + if (frang_cfg.http_body_len && + tfw_str_len(&req->body) > frang_cfg.http_body_len) { TFW_DBG("frang: http_body_len limit is reached\n"); return TFW_BLOCK; } @@ -345,7 +343,7 @@ frang_http_methods_check(const TfwHttpReq *req) { unsigned long m = (1 << req->method); - if (frang_http_methods_mask && (frang_http_methods_mask & m)) + if (frang_cfg.http_methods_mask && (frang_cfg.http_methods_mask & m)) return TFW_PASS; TFW_DBG("frang: forbidden method: %d (%#lx)\n", req->method, m); @@ -360,7 +358,8 @@ frang_http_ct_check(const TfwHttpReq *req) TfwStr *ct, *end; FrangCtVal *curr; - if (!frang_http_ct_is_required || req->method != TFW_HTTP_METH_POST) + if (!frang_cfg.http_ct_is_required || !frang_cfg.http_ct_vals || + req->method != TFW_HTTP_METH_POST) return TFW_PASS; /* Find the Content-Type header. @@ -390,13 +389,11 @@ frang_http_ct_check(const TfwHttpReq *req) * TODO: don't store field name in the TfwStr. Store only the header * value, and thus get rid of the nasty tfw_str_eq_kv(). */ - rcu_read_lock(); - for (curr = rcu_dereference(frang_http_ct_vals); curr->str; ++curr) { + for (curr = frang_cfg.http_ct_vals; curr->str; ++curr) { if (tfw_str_eq_kv(ct, _CT, _CTLEN, ':', curr->str, curr->len, TFW_STR_EQ_PREFIX_CASEI)) break; } - rcu_read_unlock(); if (!curr->str) { TFW_DBG("frang: forbidden Content-Type value\n"); @@ -413,7 +410,7 @@ frang_http_host_check(const TfwHttpReq *req) { TfwStr *field; - if (!frang_http_host_is_required || req->method != TFW_HTTP_METH_POST) + if (!frang_cfg.http_host_is_required || req->method != TFW_HTTP_METH_POST) return TFW_PASS; field = &req->h_tbl->tbl[TFW_HTTP_HDR_HOST].field; @@ -461,130 +458,52 @@ static TfwClassifier frang_class_ops = { .classify_conn_close = frang_conn_close, }; +static const TfwCfgEnum frang_http_methods_enum[] = { + { "get", TFW_HTTP_METH_GET }, + { "post", TFW_HTTP_METH_POST }, + { "head", TFW_HTTP_METH_HEAD }, + {} +}; + static int -frang_parse_int(char *tmp_buf, int *out_val) +frang_set_methods_mask(TfwCfgSpec *cs, TfwCfgEntry *ce) { - char *p; - int val = 0; - - for (p = tmp_buf; *p; ++p) { - if (!isdigit(*p)) { - TFW_ERR("not a digit: '%c'\n", *p); + int i, r, method_id; + const char *method_str; + unsigned long methods_mask = 0; + + TFW_CFG_ENTRY_FOR_EACH_VAL(ce, i, method_str) { + r = tfw_cfg_map_enum(frang_http_methods_enum, method_str, + &method_id); + if (r) { + TFW_ERR("frang: invalid method: '%s'\n", method_str); return -EINVAL; } - val = val * 10 + *p - '0'; - } - *out_val = val; - return 0; -} - -static int -frang_parse_bool(char *tmp_buf, bool *out_bool) -{ - char c = *tmp_buf; - - /* XXX: should we support true/false/etc instead of 0/1 here? */ - if (c != '0' && c != '1') { - TFW_ERR("invalid boolean value: %s\n", tmp_buf); - return -EINVAL; + TFW_DBG("frang: parsed method: %s => %d\n", + method_str, method_id); + methods_mask |= (1 << method_id); } - *out_bool = c - '0'; + TFW_DBG("parsed methods_mask: %#lx\n", methods_mask); + frang_cfg.http_methods_mask = methods_mask; return 0; } -/* TODO: refactoring: get rid of these sysctl handlers, - * replace them with Tempesta's cfg framework. */ - -static int -frang_find_idx_by_str(const char **str_vec, size_t vec_size, const char *str) -{ - int i; - const char *curr_str; - - for (i = 0; i < vec_size; ++i) { - curr_str = str_vec[i]; - BUG_ON(!curr_str); - if (!strcasecmp(curr_str, str)) - return i; - } - - return -1; -} - -#define FRANG_TOKEN_SEPARATORS " \r\n\t" - -static char * -frang_tokenize(char **tmp_buf) -{ - char *token = strsep(tmp_buf, FRANG_TOKEN_SEPARATORS); - - /* Unlike strsep(), don't return empty tokens. */ - if (token && !*token) - token = NULL; - - return token; -} - -static int -frang_count_tokens(char *str) -{ - int n = 0; - - while (*str) { - str += strspn(str, FRANG_TOKEN_SEPARATORS); - if (*str) - ++n; - str += strcspn(str, FRANG_TOKEN_SEPARATORS); - } - - return n; -} - -static int -frang_parse_methods_mask(char *tmp_buf, unsigned long *out_mask) +static void +frang_clear_methods_mask(TfwCfgSpec *cs) { - static const char *strs[_TFW_HTTP_METH_COUNT] = { - [TFW_HTTP_METH_GET] = "get", - [TFW_HTTP_METH_POST] = "post", - [TFW_HTTP_METH_HEAD] = "head", - }; - char *token; - int idx; - unsigned long methods_mask; - - token = tmp_buf; - methods_mask = 0; - while ((token = frang_tokenize(&tmp_buf))) { - idx = frang_find_idx_by_str(strs, ARRAY_SIZE(strs), token); - if (idx < 0) { - TFW_ERR("frang: invalid method: '%s'\n", token); - return -EINVAL; - } - - TFW_DBG("frang: parsed method: %s => %d\n", token, idx); - methods_mask |= (1 << idx); - } - - TFW_DBG("parsed methods_mask: %#lx\n", methods_mask); - *out_mask = methods_mask; - return 0; + frang_cfg.http_methods_mask = 0; } static int -frang_parse_ct_vals(char *tmp_buf, void *unused) +frang_set_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) { void *mem; - char *token, *strs, *strs_pos; - size_t tokens_n, vals_size, strs_size; - FrangCtVal *vals, *vals_pos, *old_vals; - - tokens_n = frang_count_tokens(tmp_buf); - if (!tokens_n) { - TFW_ERR("the frang_http_ct_vals is empty\n"); - return -EINVAL; - } + const char *in_str; + char *strs, *strs_pos; + FrangCtVal *vals, *vals_pos; + size_t i, strs_size, vals_n, vals_size; /* Allocate a single chunk of memory which is suitable to hold the * variable-sized list of variable-sized strings. @@ -599,221 +518,130 @@ frang_parse_ct_vals(char *tmp_buf, void *unused) * | | * +---------------------------+ */ - strs_size = strlen(tmp_buf) + 1; - vals_size = sizeof(FrangCtVal) * (tokens_n + 1); + vals_n = ce->val_n; + vals_size = sizeof(FrangCtVal) * (vals_n + 1); + strs_size = 0; + TFW_CFG_ENTRY_FOR_EACH_VAL(ce, i, in_str) { + strs_size += strlen(in_str) + 1; + } mem = kzalloc(vals_size + strs_size, GFP_KERNEL); + if (!mem) + return -ENOMEM; vals = mem; strs = mem + vals_size; - /* Copy tokens from tmp_buf to the vals/strs list. */ + /* Copy tokens to the new vals/strs list. */ /* TODO: validate tokens, they should look like: "text/plain". */ vals_pos = vals; strs_pos = strs; - while ((token = frang_tokenize(&tmp_buf))) { - size_t len = strlen(token) + 1; - BUG_ON(!len); + TFW_CFG_ENTRY_FOR_EACH_VAL(ce, i, in_str) { + size_t len = strlen(in_str) + 1; - memcpy(strs_pos, token, len); + memcpy(strs_pos, in_str, len); vals_pos->str = strs_pos; vals_pos->len = (len - 1); - TFW_DBG("parsed Content-Type value: '%s'\n", strs_pos); + + TFW_DBG("parsed Content-Type value: '%s'\n", in_str); vals_pos++; strs_pos += len; } - BUG_ON(vals_pos != (vals + tokens_n)); - BUG_ON(strs_pos > (strs + strs_size)); - - /* Replace the old list of allowed Content-Type values. */ - /* TODO: sort values to make binary search possible. */ - old_vals = frang_http_ct_vals; - rcu_assign_pointer(frang_http_ct_vals, vals); - synchronize_rcu(); - kfree(old_vals); + BUG_ON(vals_pos != (vals + vals_n)); + BUG_ON(strs_pos != (strs + strs_size)); + + frang_cfg.http_ct_vals = vals; return 0; } -static int -frang_sysctl_handle(ctl_table *ctl, int write, - void __user *buffer, size_t *lenp, loff_t *ppos) +static void +frang_free_ct_vals(TfwCfgSpec *cs) { - int r, len; - char *tmp_buf, *trimmed_buf; - void *parse_dest; - int (*parse_fn)(const char *tmp_buf, void *dest); - - tmp_buf = NULL; - parse_fn = ctl->extra1; - parse_dest = ctl->extra2; - BUG_ON(!parse_fn); - - if (write) { - tmp_buf = kzalloc(ctl->maxlen + 1, GFP_KERNEL); - if (!tmp_buf) { - TFW_ERR("frang: can't allocate temporary buffer\n"); - r = -ENOMEM; - goto out; - } - - len = min(ctl->maxlen, (int)*lenp); - if (copy_from_user(tmp_buf, buffer, len)) { - TFW_ERR("frang: can't copy data from userspace\n"); - r = -EFAULT; - goto out; - } - - trimmed_buf = strim(tmp_buf); - if (parse_fn(trimmed_buf, parse_dest)) { - TFW_ERR("frang: can't parse input data\n"); - r = -EINVAL; - goto out; - } - } - - r = proc_dostring(ctl, write, buffer, lenp, ppos); - if (r) - TFW_ERR("frang: sysctl error\n"); -out: - if (r) - TFW_ERR("frang: can't read/write parameter: %s\n", - ctl->procname); - kfree(tmp_buf); - return r; + kfree(frang_cfg.http_ct_vals); + frang_cfg.http_ct_vals = NULL; } -#define FRANG_INT_LEN 10 -#define FRANG_STR_LEN 255 -#define FRANG_LONG_STR_LEN 1024 - -char frang_req_rate_str[FRANG_INT_LEN]; -char frang_req_burst_str[FRANG_INT_LEN]; -char frang_conn_rate_str[FRANG_INT_LEN]; -char frang_conn_burst_str[FRANG_INT_LEN]; -char frang_conn_max_str[FRANG_INT_LEN]; -char frang_http_uri_len_str[FRANG_INT_LEN]; -char frang_http_field_len_str[FRANG_INT_LEN]; -char frang_http_body_len_str[FRANG_INT_LEN]; -char frang_http_methods_str[FRANG_STR_LEN]; -char frang_http_ct_is_required_str[FRANG_INT_LEN]; -char frang_http_content_types_str[FRANG_LONG_STR_LEN]; -char frang_http_host_is_required_str[FRANG_INT_LEN]; - -static ctl_table frang_ctl_table[] = { +static TfwCfgSpec frang_cfg_section_specs[] = { { - .procname = "request_rate", - .data = frang_req_rate_str, - .maxlen = sizeof(frang_req_rate_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_req_rate, + "request_rate", "0", + tfw_cfg_set_int, + &frang_cfg.req_rate, }, { - .procname = "request_burst", - .data = frang_req_burst_str, - .maxlen = sizeof(frang_req_burst_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_req_burst, + "request_burst", "0", + tfw_cfg_set_int, + &frang_cfg.req_burst, }, { - .procname = "new_connection_rate", - .data = frang_conn_rate_str, - .maxlen = sizeof(frang_conn_rate_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_conn_rate, + "new_connection_rate", "0", + tfw_cfg_set_int, + &frang_cfg.conn_rate, }, { - .procname = "new_connection_burst", - .data = frang_conn_burst_str, - .maxlen = sizeof(frang_conn_burst_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_conn_burst, + "new_connection_burst", "0", + tfw_cfg_set_int, + &frang_cfg.conn_burst, }, { - .procname = "concurrent_connections", - .data = frang_conn_max_str, - .maxlen = sizeof(frang_conn_max_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_conn_max, + "concurrent_connections", "0", + tfw_cfg_set_int, + &frang_cfg.conn_max, }, { - .procname = "http_uri_len", - .data = frang_http_uri_len_str, - .maxlen = sizeof(frang_http_uri_len_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_http_uri_len, + "http_uri_len", "0", + tfw_cfg_set_int, + &frang_cfg.http_uri_len, }, { - .procname = "http_field_len", - .data = frang_http_field_len_str, - .maxlen = sizeof(frang_http_field_len_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_http_field_len, + "http_field_len", "0", + tfw_cfg_set_int, + &frang_cfg.http_field_len, }, { - .procname = "http_body_len", - .data = frang_http_body_len_str, - .maxlen = sizeof(frang_http_body_len_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_int, - .extra2 = &frang_http_body_len, + "http_body_len", "0", + tfw_cfg_set_int, + &frang_cfg.http_body_len, }, { - .procname = "http_methods", - .data = frang_http_methods_str, - .maxlen = sizeof(frang_http_methods_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_methods_mask, - .extra2 = &frang_http_methods_mask, + "http_host_is_required", "false", + tfw_cfg_set_bool, + &frang_cfg.http_host_is_required, }, { - .procname = "http_ct_is_required", - .data = frang_http_ct_is_required_str, - .maxlen = sizeof(frang_http_ct_is_required_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_bool, - .extra2 = &frang_http_ct_is_required, + "http_ct_is_required", "false", + tfw_cfg_set_bool, + &frang_cfg.http_ct_is_required, }, { - .procname = "http_ct_vals", - .data = frang_http_content_types_str, - .maxlen = sizeof(frang_http_content_types_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_ct_vals, + "http_methods", NULL, + frang_set_methods_mask, + .cleanup = frang_clear_methods_mask, }, { - .procname = "http_host_is_required", - .data = frang_http_host_is_required_str, - .maxlen = sizeof(frang_http_host_is_required_str), - .mode = 0644, - .proc_handler = frang_sysctl_handle, - .extra1 = frang_parse_bool, - .extra2 = &frang_http_host_is_required + "http_ct_vals", NULL, + frang_set_ct_vals, + .cleanup = frang_free_ct_vals }, {} }; +static TfwCfgSpec frang_cfg_toplevel_specs[] = { + { + "frang_limits", NULL, + tfw_cfg_handle_children, + &frang_cfg_section_specs + }, + {} +}; + +static TfwCfgMod frang_cfg_mod = { + .name = "frang", + .specs = frang_cfg_toplevel_specs +}; + static int __init frang_init(void) { int r; - struct ctl_table_header *frang_ctl; frang_mem_cache = KMEM_CACHE(frang_account_t, 0); if (!frang_mem_cache) { @@ -821,12 +649,10 @@ frang_init(void) return -EINVAL; } - frang_ctl = register_net_sysctl(&init_net, "net/tempesta/frang", - frang_ctl_table); - if (!frang_ctl) { - TFW_ERR("frang: can't register sysctl table\n"); - r = -1; - goto err_sysctl; + r = tfw_cfg_mod_register(&frang_cfg_mod); + if (r) { + TFW_ERR("frang: can't register as a configuration module\n"); + goto err_cfg; } r = tfw_classifier_register(&frang_class_ops); @@ -862,8 +688,8 @@ frang_init(void) err_fsm: tfw_classifier_unregister(); err_class: - unregister_sysctl_table(frang_ctl); -err_sysctl: + tfw_cfg_mod_unregister(&frang_cfg_mod); +err_cfg: kmem_cache_destroy(frang_mem_cache); return r; } From 4fc4881108d91370cfc41834915c42533e3b7017 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Mon, 30 Mar 2015 19:51:08 +0300 Subject: [PATCH 15/28] frang: resolve conflicts with 30f0e6dd24cd910a --- tempesta_fw/classifier/frang.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 1243c0c26..7c0fe7b55 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -282,7 +282,7 @@ frang_http_uri_len_limit(const TfwHttpReq *req) /* FIXME: tfw_str_len() iterates over chunks to calculate the length. * This is too slow. The value must be stored in a TfwStr field. */ if (frang_cfg.http_uri_len && - tfw_str_len(&req->uri) > frang_cfg.http_uri_len) { + tfw_str_len(&req->uri_path) > frang_cfg.http_uri_len) { TFW_DBG("frang: http_uri_len limit is reached\n"); return TFW_BLOCK; } @@ -431,9 +431,10 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) { int r; TfwConnection *c = (TfwConnection *)obj; + TfwClient *clnt = (TfwClient *)c->peer; TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); - r = frang_account_do(c->sess->cli->sock, frang_req_limit); + r = frang_account_do(clnt->sock, frang_req_limit); if (r) return r; From cd752c5b2f37340eb2d70c5d68775d3bfabbb024 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 1 Apr 2015 16:18:55 +0300 Subject: [PATCH 16/28] gfsm: several fixes to call the frang hook --- tempesta_fw/gfsm.c | 15 +++++++++------ tempesta_fw/http.c | 11 +++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tempesta_fw/gfsm.c b/tempesta_fw/gfsm.c index 4876b414b..c62687bea 100644 --- a/tempesta_fw/gfsm.c +++ b/tempesta_fw/gfsm.c @@ -72,10 +72,10 @@ tfw_gfsm_state_init(TfwGState *st, void *obj, int st0) * This function is responsible for all context storing/restoring logic. */ static void -tfw_gfsm_switch(TfwGState *st, int prio) +tfw_gfsm_switch(TfwGState *st, unsigned short new_st, int prio) { - int curr_st = TFW_GFSM_STATE_ST(st), curr_fsm = TFW_GFSM_FSM(st); - int shift = prio * TFW_GFSM_PRIO_N + curr_st; + int curr_fsm = TFW_GFSM_FSM(st); + int shift = prio * TFW_GFSM_PRIO_N + new_st; SsProto *proto = (SsProto *)st->obj; if (unlikely(st->st_p + 1 >= TFW_GFSM_STACK_DEPTH)) { @@ -95,6 +95,9 @@ tfw_gfsm_switch(TfwGState *st, int prio) * as enter sate argument of tfw_gfsm_register_hook(). */ proto->type = fsm_hooks[curr_fsm][shift].fsm_id; + + /* Push the parent's st->obj to the new FSM. */ + st->obj = proto; } /** @@ -141,7 +144,7 @@ tfw_gfsm_move(TfwGState *st, unsigned short new_state, unsigned char *data, { int r = TFW_PASS, p; unsigned int *wc = st->wish_call[st->st_p]; - unsigned long mask = 1 << TFW_GFSM_STATE_ST(st); + unsigned long mask = 1 << new_state; /* Start from higest priority. */ for (p = TFW_GFSM_HOOK_PRIORITY_HIGH; @@ -149,10 +152,10 @@ tfw_gfsm_move(TfwGState *st, unsigned short new_state, unsigned char *data, { /* The bitmask is likely spread. */ if (likely(!(wc[p] & mask))) - return r; + continue; /* Switch context to other FSM. */ - tfw_gfsm_switch(st, p); + tfw_gfsm_switch(st, new_state, p); /* * Let the FSM do all its jobs. * There is possible recursion when the new FSM moves through diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index 6a473560d..1d2c11b47 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -1138,13 +1138,13 @@ tfw_http_req_process(TfwConnection *conn, unsigned char *data, size_t len) ; } - tfw_http_establish_skb_hdrs((TfwHttpMsg *)req); - r = tfw_gfsm_move(&req->msg.state, - TFW_HTTP_FSM_REQ_MSG, data, len); + /* The request is fully parsed, move to a corresponding state.*/ + r = tfw_gfsm_move(&req->msg.state, TFW_HTTP_FSM_REQ_MSG, data, + len); TFW_DBG("GFSM return code %d\n", r); if (r == TFW_BLOCK) goto block; - conn->msg = NULL; + r = tfw_http_sticky_req_process((TfwHttpMsg *)req); if (r < 0) { @@ -1161,9 +1161,8 @@ tfw_http_req_process(TfwConnection *conn, unsigned char *data, size_t len) " for a session\n"); goto block; } - /* Request is fully parsed, add it to the connection. */ + /* Add it to the server connection. */ list_add_tail(&req->msg.msg_list, &srv_conn->msg_queue); - tfw_cache_req_process(req, tfw_http_req_cache_cb, srv_conn); pipeline: if (!req->parser.data_off || req->parser.data_off == len) From 3d8b3f4f3bc42bd2522b6972e72f32afe92b0ede Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 1 Apr 2015 16:19:46 +0300 Subject: [PATCH 17/28] frang: fix wrong order of arguments for tfw_gfsm_register_hook() --- tempesta_fw/classifier/frang.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 7c0fe7b55..15c6dd4e5 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -673,8 +673,7 @@ frang_init(void) } r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_REQ_MSG, 0, - TFW_FSM_FRANG); + TFW_HTTP_FSM_REQ_MSG, TFW_FSM_FRANG, 0); if (r) { TFW_ERR("frang: can't register gfsm hook: req\n"); goto err_hook; From 05aace07e55feeae3eaa9ef7c260e0c37d3ae863 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 1 Apr 2015 16:21:44 +0300 Subject: [PATCH 18/28] classifier: add DEBUG mode compiler options to the Makefile --- tempesta_fw/classifier/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempesta_fw/classifier/Makefile b/tempesta_fw/classifier/Makefile index 201cff6e9..dbbc7bfdf 100644 --- a/tempesta_fw/classifier/Makefile +++ b/tempesta_fw/classifier/Makefile @@ -17,7 +17,7 @@ # Temple Place - Suite 330, Boston, MA 02111-1307, USA. EXTRA_CFLAGS += -I$(src)/../../sync_socket -I$(src)/../../tempesta_db/core \ - -DDEBUG -Werror + -DDEBUG -Werror -O0 -g3 obj-m = tfw_frang.o tfw_frang-objs = frang.o From 2027bb573e3e963625335787baceb9b7fece7814 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilyanov Date: Wed, 1 Apr 2015 16:25:38 +0300 Subject: [PATCH 19/28] frang: flatten uri/header/body length limiting code --- tempesta_fw/classifier/frang.c | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 15c6dd4e5..79e79f3dd 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -320,24 +320,6 @@ frang_http_body_len_limit(const TfwHttpReq *req) return TFW_PASS; } -static int -frang_http_len_limit(const TfwHttpReq *req) -{ - int r; - - r = frang_http_uri_len_limit(req); - if (r) - return r; - r = frang_http_field_len_limit(req); - if (r) - return r; - r = frang_http_body_len_limit(req); - if (r) - return r; - - return 0; -} - static int frang_http_methods_check(const TfwHttpReq *req) { @@ -441,7 +423,13 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) r = frang_http_methods_check(req); if (r) return r; - r = frang_http_len_limit(req); + r = frang_http_uri_len_limit(req); + if (r) + return r; + r = frang_http_field_len_limit(req); + if (r) + return r; + r = frang_http_body_len_limit(req); if (r) return r; r = frang_http_ct_check(req); From 37292959a7d78c54c35c1db40eb173cd89b9a3dd Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Fri, 8 May 2015 01:27:29 +0300 Subject: [PATCH 20/28] Remove error reporting from the library function tfw_addr_pton() Move error reporting outside, so that the function can be used to check whether a string is an IP address, and not only when actual conversion is required. --- tempesta_fw/addr.c | 2 -- tempesta_fw/sock_clnt.c | 2 +- tempesta_fw/sock_srv.c | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tempesta_fw/addr.c b/tempesta_fw/addr.c index 62701dfae..aaf4eb866 100644 --- a/tempesta_fw/addr.c +++ b/tempesta_fw/addr.c @@ -231,8 +231,6 @@ tfw_addr_pton(const char *str, TfwAddr *addr) else if (mode == 6) ret = tfw_addr_pton_v6(str, &addr->v6); - if (ret) - TFW_ERR("Can't parse IP address: '%s'\n", str); return ret; } DEBUG_EXPORT_SYMBOL(tfw_addr_pton); diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 6e817baf3..335305977 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -365,7 +365,7 @@ tfw_sock_clnt_cfg_handle_listen(TfwCfgSpec *cs, TfwCfgEntry *ce) return tfw_listen_sock_add(&addr, TFW_FSM_HTTP); parse_err: - TFW_ERR("can't parse 'listen' value: '%s'\n", in_str); + TFW_ERR("Unable to parse 'listen' value: '%s'\n", in_str); return -EINVAL; } diff --git a/tempesta_fw/sock_srv.c b/tempesta_fw/sock_srv.c index 7d3038dee..92dd4834a 100644 --- a/tempesta_fw/sock_srv.c +++ b/tempesta_fw/sock_srv.c @@ -579,7 +579,6 @@ tfw_srv_cfg_begin_srv_group(TfwCfgSpec *cs, TfwCfgEntry *ce) TFW_ERR("can't add srv_group: %s\n", name); return -EINVAL; } - r = tfw_sg_set_sched(sg, sched_str); if (r) { TFW_ERR("can't set scheduler for srv_group: %s\n", name); From 404e640da5b8f07322de00cbe21a47457caa3a20 Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Fri, 8 May 2015 01:30:23 +0300 Subject: [PATCH 21/28] Call connection hooks only when they actually exist Connection hooks are callback functions that are register for a particular FSM. It became obvious that not all FSM need to have these hooks, so calling these unconditionally is no longer correct. Call these hooks only when they are registered for an FSM. --- tempesta_fw/connection.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tempesta_fw/connection.c b/tempesta_fw/connection.c index b37e67635..b4bba1789 100644 --- a/tempesta_fw/connection.c +++ b/tempesta_fw/connection.c @@ -66,6 +66,8 @@ tfw_connection_unlink_sk(TfwConnection *conn) BUG_ON(!conn->sk || !conn->sk->sk_user_data); conn->sk->sk_user_data = NULL; conn->sk = NULL; + if (conn_hooks[idx]) + conn_hooks[idx]->conn_destruct(c); } void From 117d441aaf4ce6bc4b9100b6d862c2c84ed33a12 Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Fri, 8 May 2015 10:36:13 +0300 Subject: [PATCH 22/28] Rework Frang classifier and add extra features to it Frang classifier reworked to use an internal FSM that is part of GFSM. Most important extra features implemented were tracking of duplicate fields, and and header/body timeouts. Other features were corrected where necessary. --- etc/tfw_frang.conf | 17 +- tempesta_fw/classifier/frang.c | 425 +++++++++++++++++++++++++-------- tempesta_fw/http.c | 5 +- tempesta_fw/http.h | 43 +++- tempesta_fw/http_parser.c | 83 ++++++- tempesta_fw/str.c | 4 +- tempesta_fw/str.h | 3 + 7 files changed, 450 insertions(+), 130 deletions(-) diff --git a/etc/tfw_frang.conf b/etc/tfw_frang.conf index c3207fa1f..53af9f1dd 100644 --- a/etc/tfw_frang.conf +++ b/etc/tfw_frang.conf @@ -12,16 +12,19 @@ # frang_limits { # request_rate NUM; # request_burst NUM; -# new_connection_rate NUM; -# new_connection_burst NUM; +# connection_rate NUM; +# connection_burst NUM; # concurrent_connections NUM; +# client_header_timeout NUM; +# client_body_timeout NUM; # http_uri_len NUM; # http_field_len NUM; # http_body_len NUM; -# http_host_is_required true|false; +# http_host_required true|false; # http_methods [METHOD]...; -# http_ct_is_required true|false; +# http_ct_required true|false; # http_ct_vals ["CONTENT_TYPE"]...; +# http_field_noduplicate true|false; # } # # - options with names *_rate define requests/connections rate per second. @@ -32,11 +35,13 @@ # frang_limits { # request_rate 20; # request_burst 15; -# new_connection_rate 8; +# connection_rate 8; # concurrent_connections 8; +# client_header_timeout 20; +# client_body_timeout 10; # http_uri_len 1024; # http_field_len 256; -# http_ct_is_required false; +# http_ct_required false; # http_methods get post head; # http_ct_vals "text/plain" "text/html"; # } diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 79e79f3dd..90fd130e5 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -38,6 +38,7 @@ #include #include "../tempesta_fw.h" +#include "../addr.h" #include "../classifier.h" #include "../client.h" #include "../connection.h" @@ -97,12 +98,20 @@ typedef struct { unsigned int conn_burst; unsigned int conn_max; + /* + * Limits on time it takes to receive + * a full header or a body chunk. + */ + unsigned long clnt_hdr_timeout; + unsigned long clnt_body_timeout; + /* Limits for HTTP request contents: uri, headers, body, etc. */ unsigned int http_uri_len; unsigned int http_field_len; unsigned int http_body_len; - bool http_ct_is_required; - bool http_host_is_required; + bool http_ct_required; + bool http_host_required; + bool http_field_noduplicate; /* The bitmask of allowed HTTP Method values. */ unsigned long http_methods_mask; /* The list of allowed Content-Type values. */ @@ -148,6 +157,7 @@ frang_account_do(struct sock *sk, int (*func)(FrangAcc *ra, struct sock *sk)) if (ra->last_ts + GC_TO < jiffies / HZ) hash_del(&ra->hentry); } + if (!ra) { spin_unlock(&hb->lock); @@ -268,8 +278,8 @@ frang_req_limit(FrangAcc *ra, struct sock *sk) block: /* - * TODO reset connection istead of wasting resources on - * gentle closing. See ss_do_close() in sync_socket. + * TODO reset connection instead of wasting resources + * on gentle closing. See ss_do_close() in sync_socket. */ TFW_DBG("%s: close connection\n", __FUNCTION__); ss_close(sk); @@ -277,111 +287,106 @@ frang_req_limit(FrangAcc *ra, struct sock *sk) } static int -frang_http_uri_len_limit(const TfwHttpReq *req) +frang_http_uri_len(const TfwHttpReq *req) { /* FIXME: tfw_str_len() iterates over chunks to calculate the length. * This is too slow. The value must be stored in a TfwStr field. */ - if (frang_cfg.http_uri_len && - tfw_str_len(&req->uri_path) > frang_cfg.http_uri_len) { - TFW_DBG("frang: http_uri_len limit is reached\n"); + if (tfw_str_len(&req->uri_path) > frang_cfg.http_uri_len) { + TFW_DBG("frang: http_uri_len limit reached\n"); return TFW_BLOCK; } - return TFW_PASS; } static int -frang_http_field_len_limit(const TfwHttpReq *req) +frang_http_field_len_raw(const TfwHttpReq *req) { const TfwStr *field, *end; - if (!frang_cfg.http_field_len) - return TFW_PASS; - - TFW_HTTP_FOR_EACH_HDR_FIELD(field, end, req) { + FOR_EACH_HDR_FIELD_FROM(field, end, req, req->hdr_rawid) { if (tfw_str_len(field) > frang_cfg.http_field_len) { - TFW_DBG("frang: http_field_len limit is reached\n"); + TFW_DBG("frang: http_field_len limit reached\n"); return TFW_BLOCK; } } - return TFW_PASS; } static int -frang_http_body_len_limit(const TfwHttpReq *req) +frang_http_field_len_special(const TfwHttpReq *req) { - if (frang_cfg.http_body_len && - tfw_str_len(&req->body) > frang_cfg.http_body_len) { - TFW_DBG("frang: http_body_len limit is reached\n"); - return TFW_BLOCK; - } + const TfwStr *field, *end; + FOR_EACH_HDR_FIELD_SPECIAL(field, end, req) { + if (tfw_str_len(field) > frang_cfg.http_field_len) { + TFW_DBG("frang: http_field_len limit reached\n"); + return TFW_BLOCK; + } + } return TFW_PASS; } static int -frang_http_methods_check(const TfwHttpReq *req) +frang_http_methods(const TfwHttpReq *req) { - unsigned long m = (1 << req->method); - - if (frang_cfg.http_methods_mask && (frang_cfg.http_methods_mask & m)) - return TFW_PASS; + unsigned long mbit = (1 << req->method); - TFW_DBG("frang: forbidden method: %d (%#lx)\n", req->method, m); - return TFW_BLOCK; + if (!(frang_cfg.http_methods_mask & mbit)) { + TFW_DBG("frang: method not permitted: %d (%#lx)\n", + req->method, mbit); + return TFW_BLOCK; + } + return TFW_PASS; } static int frang_http_ct_check(const TfwHttpReq *req) { -#define _CT "Content-Type" -#define _CTLEN (sizeof(_CT) - 1) - TfwStr *ct, *end; +#define _CT "Content-Type" +#define _CTLEN (sizeof(_CT) - 1) + TfwStr *field, *end; FrangCtVal *curr; - if (!frang_cfg.http_ct_is_required || !frang_cfg.http_ct_vals || - req->method != TFW_HTTP_METH_POST) + if (req->method != TFW_HTTP_METH_POST) { return TFW_PASS; - + } /* Find the Content-Type header. * - * XXX: Should we make the header "special"? - * It would speed up this function, but bloat the HTTP parser code, - * and pollute the headers table. + * XXX: Make Content-Type header "special". */ - TFW_HTTP_FOR_EACH_RAW_HDR_FIELD(ct, end, req) { - if (tfw_str_eq_cstr(ct, _CT, _CTLEN, TFW_STR_EQ_PREFIX_CASEI)) + FOR_EACH_HDR_FIELD_RAW(field, end, req) { + if (tfw_str_eq_cstr(field, _CT, _CTLEN, + TFW_STR_EQ_PREFIX_CASEI)) { break; - + } } - if (ct == end) { + if (field == end) { TFW_DBG("frang: Content-Type is missing\n"); return TFW_BLOCK; } - - /* Check that the Content-Type is in the list of allowed values. + /* Verify that Content-Type value is on the list of allowed values. * * TODO: possible improvement: binary search. - * Generally the binary search is more efficient, but linear search is - * usually faster for small sets of values. Perhaps we should switch - * between two if the performance is that critical here, but benchmarks - * should be done to measure the impact. + * Generally binary search is more efficient, but linear search + * is usually faster for small sets of values. Perhaps we should + * switch between the two if performance is critical here, + * but benchmarks should be done to measure the impact. * - * TODO: don't store field name in the TfwStr. Store only the header - * value, and thus get rid of the nasty tfw_str_eq_kv(). + * TODO: don't store field name in the TfwStr. Store only + * the header field value, and thus get rid of tfw_str_eq_kv(). */ for (curr = frang_cfg.http_ct_vals; curr->str; ++curr) { - if (tfw_str_eq_kv(ct, _CT, _CTLEN, ':', curr->str, curr->len, - TFW_STR_EQ_PREFIX_CASEI)) + if (tfw_str_eq_kv(field, _CT, _CTLEN, ':', + curr->str, curr->len, + TFW_STR_EQ_PREFIX_CASEI)) { break; + } } - if (!curr->str) { - TFW_DBG("frang: forbidden Content-Type value\n"); + TFW_DBG("frang: Content-Type value not permitted: %s\n", + curr->str); return TFW_BLOCK; } - return TFW_PASS; #undef _CT #undef _CTLEN @@ -390,56 +395,240 @@ frang_http_ct_check(const TfwHttpReq *req) static int frang_http_host_check(const TfwHttpReq *req) { + int len; TfwStr *field; - - if (!frang_cfg.http_host_is_required || req->method != TFW_HTTP_METH_POST) - return TFW_PASS; + TfwAddr addr; + char *buf, *ptr; field = &req->h_tbl->tbl[TFW_HTTP_HDR_HOST].field; if (!field->ptr) { TFW_DBG("frang: the Host header is missing\n"); return TFW_BLOCK; } - - /* FIXME: here should be a check that the Host value is not an IP + /* + * FIXME: here should be a check that the Host value is not an IP * address. Need a fast routine that supports compound TfwStr. * Perhaps should implement a tiny FSM or postpone the task until we - * have a good regex library. */ + * have a good regex library. + * For now just linearize the Host header field TfwStr{} string. + */ + len = tfw_str_len(field) + 1; + if ((buf = tfw_pool_alloc(req->pool, len)) == NULL) + return TFW_BLOCK; + tfw_str_to_cstr(field, buf, len); + ptr = buf + sizeof("Host:") - 1; + ptr = skip_spaces(ptr); + if (!tfw_addr_pton(ptr, &addr)) + return TFW_BLOCK; return TFW_PASS; } +enum { + Frang_Req_0, + + Frang_Req_Hdr_Start, + Frang_Req_Hdr_Method, + Frang_Req_Hdr_UriLen, + Frang_Req_Hdr_FieldDup, + Frang_Req_Hdr_FieldLenRaw, + Frang_Req_Hdr_FieldLenSpecial, + Frang_Req_Hdr_Crlf, + Frang_Req_Hdr_Host, + Frang_Req_Hdr_ContentType, + + Frang_Req_Hdr_NoState, + + Frang_Req_Body_Start, + Frang_Req_Body_Timeout, + Frang_Req_Body_Len, + + Frang_Req_Body_NoState, + + Frang_Req_NothingToDo +}; + +#define FSM_HDR_STATE(state) \ + ((state > Frang_Req_Hdr_Start) && (state < Frang_Req_Hdr_NoState)) + +#define __FSM_INIT() \ +int __fsm_const_state; + +#define __FSM_START(st) \ +switch(st) + +#define __FSM_FINISH() \ +done: \ + TFW_DBG("Finish FRANG FSM at state %d\n", __fsm_const_state); \ + TFW_DBG("Return %s\n", r == TFW_PASS ? "PASS" : "BLOCK"); \ + req->frang_st = __fsm_const_state; + +#define __FSM_STATE(st) \ +case st: \ +st: __attribute__((unused)) \ + TFW_DBG("enter FRANG FSM at state %d\n", st); \ + __fsm_const_state = st; /* optimized out to constant */ + +#define __FSM_EXIT() goto done; + +#define __FSM_JUMP(to) goto to; +#define __FSM_MOVE(to) \ + if (r) \ + __FSM_EXIT(); \ + goto to; + +#define __FSM_JUMP_EXIT(to) \ + __fsm_const_state = to; /* optimized out to constant */ \ + __FSM_EXIT() + static int frang_http_req_handler(void *obj, unsigned char *data, size_t len) { - int r; + int r = TFW_PASS; + unsigned int body_len = len; TfwConnection *c = (TfwConnection *)obj; TfwClient *clnt = (TfwClient *)c->peer; TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); + struct sk_buff *head_skb = (void *)ss_skb_peek(&req->msg.skb_list); + struct sk_buff *skb = (void *)ss_skb_peek_tail(&req->msg.skb_list); - r = frang_account_do(clnt->sock, frang_req_limit); - if (r) - return r; - - r = frang_http_methods_check(req); - if (r) - return r; - r = frang_http_uri_len_limit(req); - if (r) - return r; - r = frang_http_field_len_limit(req); - if (r) - return r; - r = frang_http_body_len_limit(req); - if (r) - return r; - r = frang_http_ct_check(req); - if (r) - return r; - r = frang_http_host_check(req); - if (r) - return r; + __FSM_INIT(); - return 0; + /* + * There's no need to check for header timeout if this is the very + * first data chunk of a request (first full separate SKB with data. + * The FSM is guaranteed to go through the initial states and then + * either block or move to one of header states. Then header timeout + * is checked on each consecutive SKB with data. + * + * Why is this not one of FSM states? Basically, that's to avoid + * going through unnecessary FSM states each time this is run. When + * there's a slowris attack, we may stay long in Hdr_Method or in + * Hdr_UriLen states, and that would require including the header + * timeout state in the loop. But when we're past these states, we + * don't want to run through them on each run again, and just loop + * in FieldDup and FieldLen states. I guess that can be done with + * some clever FSM programming, but this is plain simpler. + */ + if (frang_cfg.clnt_hdr_timeout + && (skb != head_skb) && FSM_HDR_STATE(req->frang_st)) { + unsigned long start = req->tm_header; + unsigned long delta = frang_cfg.clnt_hdr_timeout; + if (time_is_after_jiffies(start + delta)) + return TFW_BLOCK; + } + + __FSM_START(req->frang_st) { + + __FSM_STATE(Frang_Req_0) { + if (frang_cfg.req_burst || frang_cfg.req_rate) { + r = frang_account_do(clnt->sock, frang_req_limit); + } + __FSM_MOVE(Frang_Req_Hdr_Start); + } + __FSM_STATE(Frang_Req_Hdr_Start) { + if (frang_cfg.clnt_hdr_timeout) { + req->tm_header = jiffies; + } + req->hdr_rawid = TFW_HTTP_HDR_RAW; + __FSM_JUMP(Frang_Req_Hdr_Method); + } + __FSM_STATE(Frang_Req_Hdr_Method) { + if (frang_cfg.http_methods_mask) { + if (req->method == TFW_HTTP_METH_NONE) { + __FSM_EXIT(); + } + r = frang_http_methods(req); + } + __FSM_MOVE(Frang_Req_Hdr_UriLen); + } + __FSM_STATE(Frang_Req_Hdr_UriLen) { + if (frang_cfg.http_uri_len) { + if (!(req->uri_path.flags & TFW_STR_COMPLETE)) { + __FSM_EXIT(); + } + r = frang_http_uri_len(req); + } + __FSM_MOVE(Frang_Req_Hdr_FieldDup); + } + __FSM_STATE(Frang_Req_Hdr_FieldDup) { + if (frang_cfg.http_field_noduplicate) { + if (req->flags & TFW_HTTP_FIELD_DUPENTRY) { + r = TFW_BLOCK; + } + } + __FSM_MOVE(Frang_Req_Hdr_FieldLenRaw); + } + __FSM_STATE(Frang_Req_Hdr_FieldLenRaw) { + if (frang_cfg.http_field_len) { + r = frang_http_field_len_raw(req); + req->hdr_rawid = req->h_tbl->off; + } + __FSM_MOVE(Frang_Req_Hdr_Crlf); + } + __FSM_STATE(Frang_Req_Hdr_Crlf) { + if (req->crlf) { + __FSM_JUMP(Frang_Req_Hdr_FieldLenSpecial); + } + __FSM_JUMP_EXIT(Frang_Req_Hdr_FieldDup); + } + __FSM_STATE(Frang_Req_Hdr_FieldLenSpecial) { + if (frang_cfg.http_field_len) { + r = frang_http_field_len_special(req); + } + __FSM_MOVE(Frang_Req_Hdr_Host); + } + __FSM_STATE(Frang_Req_Hdr_Host) { + if (frang_cfg.http_host_required) { + r = frang_http_host_check(req); + } + __FSM_MOVE(Frang_Req_Hdr_ContentType); + } + __FSM_STATE(Frang_Req_Hdr_ContentType) { + if (frang_cfg.http_ct_required || frang_cfg.http_ct_vals) { + r = frang_http_ct_check(req); + } + __FSM_MOVE(Frang_Req_Body_Start); + } + __FSM_STATE(Frang_Req_Body_Start) { + if (frang_cfg.clnt_body_timeout) { + req->tm_bchunk = jiffies; + } + if (frang_cfg.http_body_len) { + req->body_len = 0; + body_len = tfw_str_len(&req->body); + __FSM_JUMP_EXIT(Frang_Req_Body_Len); + } + __FSM_JUMP_EXIT(Frang_Req_NothingToDo); + } + __FSM_STATE(Frang_Req_Body_Timeout) { + /* + * Note that this state is skipped on the first + * data SKB with a body part as that's unnecesary. + */ + if (frang_cfg.clnt_body_timeout && (skb != head_skb)) { + unsigned long start = req->tm_bchunk; + unsigned long delta = frang_cfg.clnt_body_timeout; + if (time_is_after_jiffies(start + delta)) + r = TFW_BLOCK; + } + __FSM_MOVE(Frang_Req_Body_Len); + } + __FSM_STATE(Frang_Req_Body_Len) { + req->body_len += body_len; + if (req->body_len > frang_cfg.http_body_len) { + TFW_DBG("frang: http_body_len limit reached\n"); + r = TFW_BLOCK; + } + __FSM_JUMP_EXIT(Frang_Req_Body_Timeout); + } + __FSM_STATE(Frang_Req_NothingToDo) { + __FSM_EXIT(); + } + + } + __FSM_FINISH(); + + return r; } static TfwClassifier frang_class_ops = { @@ -549,6 +738,17 @@ frang_free_ct_vals(TfwCfgSpec *cs) frang_cfg.http_ct_vals = NULL; } +static int +frang_start(void) +{ + /* Convert these timeouts to jiffies for convenience */ + frang_cfg.clnt_hdr_timeout = + *(unsigned int *)&frang_cfg.clnt_hdr_timeout * HZ; + frang_cfg.clnt_body_timeout = + *(unsigned int *)&frang_cfg.clnt_body_timeout * HZ; + return 0; +} + static TfwCfgSpec frang_cfg_section_specs[] = { { "request_rate", "0", @@ -561,12 +761,12 @@ static TfwCfgSpec frang_cfg_section_specs[] = { &frang_cfg.req_burst, }, { - "new_connection_rate", "0", + "connection_rate", "0", tfw_cfg_set_int, &frang_cfg.conn_rate, }, { - "new_connection_burst", "0", + "connection_burst", "0", tfw_cfg_set_int, &frang_cfg.conn_burst, }, @@ -575,6 +775,16 @@ static TfwCfgSpec frang_cfg_section_specs[] = { tfw_cfg_set_int, &frang_cfg.conn_max, }, + { + "client_header_timeout", "0", + tfw_cfg_set_int, + (unsigned int *)&frang_cfg.clnt_hdr_timeout, + }, + { + "client_body_timeout", "0", + tfw_cfg_set_int, + (unsigned int *)&frang_cfg.clnt_body_timeout, + }, { "http_uri_len", "0", tfw_cfg_set_int, @@ -591,14 +801,19 @@ static TfwCfgSpec frang_cfg_section_specs[] = { &frang_cfg.http_body_len, }, { - "http_host_is_required", "false", + "http_host_required", "false", tfw_cfg_set_bool, - &frang_cfg.http_host_is_required, + &frang_cfg.http_host_required, }, { - "http_ct_is_required", "false", + "http_ct_required", "false", tfw_cfg_set_bool, - &frang_cfg.http_ct_is_required, + &frang_cfg.http_ct_required, + }, + { + "http_field_noduplicate", "false", + tfw_cfg_set_bool, + &frang_cfg.http_field_noduplicate, }, { "http_methods", NULL, @@ -615,15 +830,16 @@ static TfwCfgSpec frang_cfg_section_specs[] = { static TfwCfgSpec frang_cfg_toplevel_specs[] = { { - "frang_limits", NULL, - tfw_cfg_handle_children, - &frang_cfg_section_specs + .name = "frang_limits", + .handler = tfw_cfg_handle_children, + .dest = &frang_cfg_section_specs }, {} }; static TfwCfgMod frang_cfg_mod = { .name = "frang", + .start = frang_start, .specs = frang_cfg_toplevel_specs }; @@ -650,24 +866,29 @@ frang_init(void) goto err_class; } - /* FIXME: this is not a FSM here, but rather a set of static checks - * that are executed when a HTTP request is fully parsed. - * These checks should be executed during the parsing process in order - * to drop suspicious requests as early as possible. */ r = tfw_gfsm_register_fsm(TFW_FSM_FRANG, frang_http_req_handler); if (r) { - TFW_ERR("frang: can't register fsm: req\n"); + TFW_ERR("frang: can't register fsm\n"); goto err_fsm; } r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_REQ_MSG, TFW_FSM_FRANG, 0); + TFW_HTTP_FSM_REQ_MSG, TFW_FSM_FRANG, + TFW_GFSM_HTTP_STATE(TFW_GFSM_STATE_LAST)); if (r) { - TFW_ERR("frang: can't register gfsm hook: req\n"); + TFW_ERR("frang: can't register gfsm hook: msg\n"); goto err_hook; } + r = tfw_gfsm_register_hook(TFW_FSM_HTTP, TFW_GFSM_HOOK_PRIORITY_ANY, + TFW_HTTP_FSM_REQ_CHUNK, TFW_FSM_FRANG, + TFW_GFSM_HTTP_STATE(TFW_GFSM_STATE_LAST)); + if (r) { + TFW_ERR("frang: can't register gfsm hook: chunk\n"); + TFW_ERR("frang: can't recover\n"); + BUG(); + } - TFW_WARN("frang mudule can't be unloaded, " + TFW_WARN("frang module can't be unloaded, " "so all allocated resources won't freed\n"); return 0; diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index 1d2c11b47..9688d0b71 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -1127,6 +1127,7 @@ tfw_http_req_process(TfwConnection *conn, unsigned char *data, size_t len) tfw_http_establish_skb_hdrs((TfwHttpMsg *)req); r = tfw_gfsm_move(&req->msg.state, TFW_HTTP_FSM_REQ_CHUNK, data, len); + TFW_DBG("GFSM return code %d\n", r); if (r == TFW_BLOCK) goto block; return TFW_POSTPONE; @@ -1139,8 +1140,8 @@ tfw_http_req_process(TfwConnection *conn, unsigned char *data, size_t len) } /* The request is fully parsed, move to a corresponding state.*/ - r = tfw_gfsm_move(&req->msg.state, TFW_HTTP_FSM_REQ_MSG, data, - len); + r = tfw_gfsm_move(&req->msg.state, + TFW_HTTP_FSM_REQ_MSG, data, len); TFW_DBG("GFSM return code %d\n", r); if (r == TFW_BLOCK) goto block; diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 7cfdaeaff..e5785b3bb 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -33,6 +33,7 @@ #define TFW_HTTP_PF_CRLF (TFW_HTTP_PF_CR | TFW_HTTP_PF_LF) typedef enum { + TFW_HTTP_METH_NONE, TFW_HTTP_METH_GET, TFW_HTTP_METH_HEAD, TFW_HTTP_METH_POST, @@ -62,14 +63,14 @@ typedef struct { * large and log(N) becomes expensive and hard to code. * * So we use states space splitting to avoid states explosion. - * @_stashed_st is used to save current state and go to inferior sub-automaton - * (e.g. process LWS using @state while current state is saved in @_stashed_st - * or using @_stashed_st parse value of a header described + * @_i_st is used to save current state and go to interior sub-automaton + * (e.g. process LWS using @state while current state is saved in @_i_st + * or using @_i_st parse value of a header described */ typedef struct tfw_http_parser { unsigned char flags; int state; /* current parser state */ - int _i_st; /* helping (inferior) state */ + int _i_st; /* helping (interior) state */ int data_off; /* data offset from which the parser starts reading */ int to_read; /* remaining data to read */ @@ -147,14 +148,25 @@ typedef struct { /** * HTTP Request. * + * @method - HTTP request method, one of GET/PORT/HEAD/etc; * @host - host in URI, may differ from Host header; * @uri_path - path + query + fragment from URI (RFC3986.3); + * @frang_st - current state of FRANG classifier; + * @hdr_rawid - id of the latest RAW header that was checked; + * @tm_header - time HTTP header started coming; + * @tm_bchunk - time previous chunk of HTTP body had come at; + * @body_len - current length of the HTTP message body; */ typedef struct { TFW_HTTP_MSG_COMMON; unsigned char method; TfwStr host; TfwStr uri_path; + unsigned int frang_st; + unsigned int hdr_rawid; + unsigned long tm_header; + unsigned long tm_bchunk; + unsigned long body_len; } TfwHttpReq; typedef struct { @@ -164,16 +176,23 @@ typedef struct { unsigned int expires; } TfwHttpResp; -#define TFW_HTTP_FOR_EACH_HDR_FIELD(pos, end, msg) \ - __TFW_HTTP_FOR_EACH_HDR_FROM(pos, end, msg, 0) +#define FOR_EACH_HDR_FIELD(pos, end, msg) \ + __FOR_EACH_HDR_FIELD(pos, end, msg, 0, (msg)->h_tbl->off) -#define TFW_HTTP_FOR_EACH_RAW_HDR_FIELD(pos, end, msg) \ - __TFW_HTTP_FOR_EACH_HDR_FROM(pos, end, msg, TFW_HTTP_HDR_RAW) +#define FOR_EACH_HDR_FIELD_SPECIAL(pos, end, msg) \ + __FOR_EACH_HDR_FIELD(pos, end, msg, 0, TFW_HTTP_HDR_RAW) -#define __TFW_HTTP_FOR_EACH_HDR_FROM(pos, end, msg, start_off) \ - for ((pos) = &(msg)->h_tbl->tbl[start_off].field, \ - (end) = &(msg)->h_tbl->tbl[(msg)->h_tbl->off].field; \ - (pos) < (end); \ +#define FOR_EACH_HDR_FIELD_RAW(pos, end, msg) \ + __FOR_EACH_HDR_FIELD(pos, end, msg, TFW_HTTP_HDR_RAW, \ + (msg)->h_tbl->off) + +#define FOR_EACH_HDR_FIELD_FROM(pos, end, msg, soff) \ + __FOR_EACH_HDR_FIELD(pos, end, msg, soff, (msg)->h_tbl->off) + +#define __FOR_EACH_HDR_FIELD(pos, end, msg, soff, eoff) \ + for ((pos) = &(msg)->h_tbl->tbl[soff].field, \ + (end) = &(msg)->h_tbl->tbl[eoff].field; \ + (pos) < (end); \ ++(pos)) typedef void (*tfw_http_req_cache_cb_t)(TfwHttpReq *, TfwHttpResp *, void *); diff --git a/tempesta_fw/http_parser.c b/tempesta_fw/http_parser.c index 912e87ed1..d94aee372 100644 --- a/tempesta_fw/http_parser.c +++ b/tempesta_fw/http_parser.c @@ -68,6 +68,7 @@ __field_finish(TfwStr *field, unsigned char *begin, unsigned char *end) BUG_ON(!field->ptr); field->len = end - (unsigned char *)field->ptr; } + field->flags |= TFW_STR_COMPLETE; } #define __FSM_START(s) \ @@ -827,6 +828,37 @@ __parse_transfer_encoding(TfwHttpMsg *msg, unsigned char *data, size_t *lenrval) return r; } +static int +__header_is_duplicate(TfwHttpMsg *hm, int id, int adjust) +{ + int dupid; + char *buf; + TfwHttpHdrTbl *ht = hm->h_tbl; + TfwStr *field = &hm->h_tbl->tbl[id].field; + int len = tfw_str_len(field) - adjust; + + if ((buf = tfw_pool_alloc(hm->pool, len + 1)) == NULL) + return id; + tfw_str_to_cstr(field, buf, len + 1); + for (dupid = TFW_HTTP_HDR_RAW; dupid < ht->off; dupid++) { + TfwStr *hdr = &ht->tbl[dupid].field; + if (tfw_str_eq_cstr(hdr, buf, len, TFW_STR_EQ_PREFIX_CASEI)) + return dupid; + } + return id; +} + +static inline char * +__strrnchr(const char *s, size_t len, int c) +{ + const char *p = s + len; + do { + if (*p == (char)c) + return (char *)p; + } while (--p >= s); + return NULL; +} + /** * TODO process duplicate _generic_ (TFW_HTT_HDR_RAW) headers like: * @@ -840,11 +872,12 @@ __parse_transfer_encoding(TfwHttpMsg *msg, unsigned char *data, size_t *lenrval) * tfw_cache_copy_resp() also must be updated. */ static void -__store_header(TfwHttpMsg *hm, unsigned char *data, long len, int id, +__store_header(TfwHttpMsg *hm, char *data, long len, int id, bool close) { - TfwHttpHdrTbl *ht = hm->h_tbl; TfwStr *h; + TfwHttpHdrTbl *ht = hm->h_tbl; + char *cptr, *hptr = hm->parser.hdr.ptr; if (unlikely(id == TFW_HTTP_HDR_RAW && hm->h_tbl->off == hm->h_tbl->size)) @@ -872,6 +905,25 @@ __store_header(TfwHttpMsg *hm, unsigned char *data, long len, int id, id = ht->off; h = &ht->tbl[id].field; + + /* + * This doesn't change the current logic, but allows for proper + * concatenation of header fields that may come in multiples in + * a single HTTP message. Things to take care of are: + * - Certain header fields are strictly singular and may not be + * repeated in an HTTP message. Duplicate of a singular header + * fields is a bug worth blocking the whole HTTP message. + * - Certain header fields have '\n' as a concatenating character + * instead of an usual ',' (comma). + */ + if ((id < TFW_HTTP_HDR_RAW) + && h->ptr && (h->flags & TFW_STR_COMPLETE)) { + hm->flags |= TFW_HTTP_FIELD_DUPENTRY; + /* XXX Make sure the header is NOT singular */ + /* XXX Append a proper concatenation character */ + /* XXX Do not append the field name, just the value */ + /* Don't change current logic for now */ + } if (h->ptr) { /* * The header consists of multiple fragments. @@ -881,16 +933,37 @@ __store_header(TfwHttpMsg *hm, unsigned char *data, long len, int id, if (!h) return; } - TFW_STR_COPY(h, &hm->parser.hdr); h->len = len; + + if ((id > TFW_HTTP_HDR_RAW) + && len && !(h->flags & TFW_STR_HASCOLON) + && ((cptr = __strrnchr(hptr, len, ':')) != NULL)) { + int dupid = __header_is_duplicate(hm, id, hptr + len - cptr); + TfwStr *duph = (dupid == id) ? NULL : &ht->tbl[dupid].field; + BUG_ON(duph && !(duph->flags & TFW_STR_COMPLETE)); + h->flags |= TFW_STR_HASCOLON; + if (duph) { + hm->flags |= TFW_HTTP_FIELD_DUPENTRY; + /* XXX Make sure the header is NOT singular */ + /* XXX Append a proper concatenation character */ + /* XXX Append accumulated data to the original field */ + /* XXX Do not append the field name, just the value */ + /* XXX Switch current field to the original one */ + /* Don't change current logic for now */ + } + } + TFW_STR_INIT(&hm->parser.hdr); TFW_DBG("store header w/ ptr=%p len=%d flags=%x id=%d\n", h->ptr, h->len, h->flags, id); /* Move the offset forward if current header is fully read. */ - if (close && (id == ht->off)) - ht->off++; + if (close) { + if (id == ht->off) + ht->off++; + h->flags |= TFW_STR_COMPLETE; + } } #define STORE_HEADER(rmsg, id, len) __store_header((TfwHttpMsg *)rmsg, \ diff --git a/tempesta_fw/str.c b/tempesta_fw/str.c index f9a2cfe79..e0845c3ed 100644 --- a/tempesta_fw/str.c +++ b/tempesta_fw/str.c @@ -105,9 +105,7 @@ tfw_str_add_compound(TfwPool *pool, TfwStr *str) TfwStr *a = tfw_pool_alloc(pool, 2 * sizeof(TfwStr)); if (!a) return NULL; - a[0].ptr = str->ptr; - a[0].len = str->len; - a[0].flags = 0; /* TODO: should we inherit flags here? */ + a[0] = *str; str->ptr = a; str->len = 2; str->flags |= TFW_STR_COMPOUND; diff --git a/tempesta_fw/str.h b/tempesta_fw/str.h index 4b9bc21a4..7c7c6c1ef 100644 --- a/tempesta_fw/str.h +++ b/tempesta_fw/str.h @@ -29,6 +29,9 @@ /* Str constists from compound strings. */ #define TFW_STR_COMPOUND2 0x02 +#define TFW_STR_COMPLETE 0x10 /* The string is complete */ +#define TFW_STR_HASCOLON 0x20 /* The string has a colon char */ + typedef struct { unsigned int flags; unsigned int len; /* length of a string or array of strings */ From ab2aecafcb0ad2a74159a7260bf0e1b3684db2cc Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Fri, 8 May 2015 12:51:42 +0300 Subject: [PATCH 23/28] Resolve/fix new/missing pieces after rebase --- tempesta_fw/Makefile | 2 +- tempesta_fw/classifier/frang.c | 7 +++---- tempesta_fw/connection.c | 2 -- tempesta_fw/http.h | 1 + 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tempesta_fw/Makefile b/tempesta_fw/Makefile index 4e25bb599..02ea8b8e1 100644 --- a/tempesta_fw/Makefile +++ b/tempesta_fw/Makefile @@ -52,4 +52,4 @@ tempesta_fw-objs = \ str.o \ tls.o -obj-m += log/ classifier/ stress/ sched/ filter/ t/ +obj-m += log/ classifier/ stress/ sched/ t/ diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 90fd130e5..572fbbd80 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -485,9 +485,8 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) { int r = TFW_PASS; unsigned int body_len = len; - TfwConnection *c = (TfwConnection *)obj; - TfwClient *clnt = (TfwClient *)c->peer; - TfwHttpReq *req = container_of(c->msg, TfwHttpReq, msg); + TfwConnection *conn = (TfwConnection *)obj; + TfwHttpReq *req = container_of(conn->msg, TfwHttpReq, msg); struct sk_buff *head_skb = (void *)ss_skb_peek(&req->msg.skb_list); struct sk_buff *skb = (void *)ss_skb_peek_tail(&req->msg.skb_list); @@ -521,7 +520,7 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) __FSM_STATE(Frang_Req_0) { if (frang_cfg.req_burst || frang_cfg.req_rate) { - r = frang_account_do(clnt->sock, frang_req_limit); + r = frang_account_do(conn->sk, frang_req_limit); } __FSM_MOVE(Frang_Req_Hdr_Start); } diff --git a/tempesta_fw/connection.c b/tempesta_fw/connection.c index b4bba1789..b37e67635 100644 --- a/tempesta_fw/connection.c +++ b/tempesta_fw/connection.c @@ -66,8 +66,6 @@ tfw_connection_unlink_sk(TfwConnection *conn) BUG_ON(!conn->sk || !conn->sk->sk_user_data); conn->sk->sk_user_data = NULL; conn->sk = NULL; - if (conn_hooks[idx]) - conn_hooks[idx]->conn_destruct(c); } void diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index e5785b3bb..c76e7b04e 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -118,6 +118,7 @@ typedef struct { /* Request flags */ #define TFW_HTTP_STICKY_SET (0x0100) /* Need 'Set-Cookie` */ +#define TFW_HTTP_FIELD_DUPENTRY (0x0200) /* Duplicate field */ /** * Common HTTP message members. From 54b0386d33b110125b18d43bf4413f33d67dd1ef Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Fri, 8 May 2015 17:28:17 +0300 Subject: [PATCH 24/28] Revert "gfsm: several fixes to call the frang hook" This reverts commit cd752c5b2f37340eb2d70c5d68775d3bfabbb024. --- tempesta_fw/gfsm.c | 15 ++++++--------- tempesta_fw/http.c | 7 ++++--- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/tempesta_fw/gfsm.c b/tempesta_fw/gfsm.c index c62687bea..4876b414b 100644 --- a/tempesta_fw/gfsm.c +++ b/tempesta_fw/gfsm.c @@ -72,10 +72,10 @@ tfw_gfsm_state_init(TfwGState *st, void *obj, int st0) * This function is responsible for all context storing/restoring logic. */ static void -tfw_gfsm_switch(TfwGState *st, unsigned short new_st, int prio) +tfw_gfsm_switch(TfwGState *st, int prio) { - int curr_fsm = TFW_GFSM_FSM(st); - int shift = prio * TFW_GFSM_PRIO_N + new_st; + int curr_st = TFW_GFSM_STATE_ST(st), curr_fsm = TFW_GFSM_FSM(st); + int shift = prio * TFW_GFSM_PRIO_N + curr_st; SsProto *proto = (SsProto *)st->obj; if (unlikely(st->st_p + 1 >= TFW_GFSM_STACK_DEPTH)) { @@ -95,9 +95,6 @@ tfw_gfsm_switch(TfwGState *st, unsigned short new_st, int prio) * as enter sate argument of tfw_gfsm_register_hook(). */ proto->type = fsm_hooks[curr_fsm][shift].fsm_id; - - /* Push the parent's st->obj to the new FSM. */ - st->obj = proto; } /** @@ -144,7 +141,7 @@ tfw_gfsm_move(TfwGState *st, unsigned short new_state, unsigned char *data, { int r = TFW_PASS, p; unsigned int *wc = st->wish_call[st->st_p]; - unsigned long mask = 1 << new_state; + unsigned long mask = 1 << TFW_GFSM_STATE_ST(st); /* Start from higest priority. */ for (p = TFW_GFSM_HOOK_PRIORITY_HIGH; @@ -152,10 +149,10 @@ tfw_gfsm_move(TfwGState *st, unsigned short new_state, unsigned char *data, { /* The bitmask is likely spread. */ if (likely(!(wc[p] & mask))) - continue; + return r; /* Switch context to other FSM. */ - tfw_gfsm_switch(st, new_state, p); + tfw_gfsm_switch(st, p); /* * Let the FSM do all its jobs. * There is possible recursion when the new FSM moves through diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index 9688d0b71..13ce8d477 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -1139,13 +1139,13 @@ tfw_http_req_process(TfwConnection *conn, unsigned char *data, size_t len) ; } - /* The request is fully parsed, move to a corresponding state.*/ + tfw_http_establish_skb_hdrs((TfwHttpMsg *)req); r = tfw_gfsm_move(&req->msg.state, TFW_HTTP_FSM_REQ_MSG, data, len); TFW_DBG("GFSM return code %d\n", r); if (r == TFW_BLOCK) goto block; - + conn->msg = NULL; r = tfw_http_sticky_req_process((TfwHttpMsg *)req); if (r < 0) { @@ -1162,8 +1162,9 @@ tfw_http_req_process(TfwConnection *conn, unsigned char *data, size_t len) " for a session\n"); goto block; } - /* Add it to the server connection. */ + /* Request is fully parsed, add it to the connection. */ list_add_tail(&req->msg.msg_list, &srv_conn->msg_queue); + tfw_cache_req_process(req, tfw_http_req_cache_cb, srv_conn); pipeline: if (!req->parser.data_off || req->parser.data_off == len) From fbe9ebdf8904081bac522b1d508cdb5796c25d54 Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Tue, 12 May 2015 14:38:34 +0300 Subject: [PATCH 25/28] Flag duplicates for singular headers only --- tempesta_fw/http_parser.c | 46 +++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/tempesta_fw/http_parser.c b/tempesta_fw/http_parser.c index d94aee372..a099025c4 100644 --- a/tempesta_fw/http_parser.c +++ b/tempesta_fw/http_parser.c @@ -828,6 +828,44 @@ __parse_transfer_encoding(TfwHttpMsg *msg, unsigned char *data, size_t *lenrval) return r; } +/* + * All singular headers should be made special and take a contiguous + * id space (be grouped together in the id space). Non-singular special + * headers should follow and take a different id space to differentiate + * from singular headers. That way checking whether a header field can + * be duplicated or not would be easy and fast as it should be. The check + * would be a simple comparison if the header field id is within the range. + */ +static bool +__header_is_singular(const TfwStr *field) +{ + int idx; +#define TfwStr_string(v) { 0, sizeof(v) - 1, (v) } + static const TfwStr field_singular[] = { + TfwStr_string("Host"), + TfwStr_string("Location"), + TfwStr_string("Content_Type"), + TfwStr_string("Content_Length"), + TfwStr_string("User_Agent"), + TfwStr_string("Referer"), + TfwStr_string("From"), + TfwStr_string("Authorization"), + TfwStr_string("Proxy_Authorization"), + TfwStr_string("If_Modified_Since"), + TfwStr_string("If_Unmodified_Since"), + TfwStr_string("Max_Forwards"), + }; +#undef TfwStr_string + + for (idx = 0; idx < ARRAY_SIZE(field_singular); idx++) + if (tfw_str_eq_cstr(field, + field_singular[idx].ptr, + field_singular[idx].len, + TFW_STR_EQ_PREFIX_CASEI)) + return true; + return false; +} + static int __header_is_duplicate(TfwHttpMsg *hm, int id, int adjust) { @@ -918,8 +956,8 @@ __store_header(TfwHttpMsg *hm, char *data, long len, int id, */ if ((id < TFW_HTTP_HDR_RAW) && h->ptr && (h->flags & TFW_STR_COMPLETE)) { - hm->flags |= TFW_HTTP_FIELD_DUPENTRY; - /* XXX Make sure the header is NOT singular */ + if (__header_is_singular(h)) + hm->flags |= TFW_HTTP_FIELD_DUPENTRY; /* XXX Append a proper concatenation character */ /* XXX Do not append the field name, just the value */ /* Don't change current logic for now */ @@ -944,8 +982,8 @@ __store_header(TfwHttpMsg *hm, char *data, long len, int id, BUG_ON(duph && !(duph->flags & TFW_STR_COMPLETE)); h->flags |= TFW_STR_HASCOLON; if (duph) { - hm->flags |= TFW_HTTP_FIELD_DUPENTRY; - /* XXX Make sure the header is NOT singular */ + if (__header_is_singular(h)) + hm->flags |= TFW_HTTP_FIELD_DUPENTRY; /* XXX Append a proper concatenation character */ /* XXX Append accumulated data to the original field */ /* XXX Do not append the field name, just the value */ From 84f064389ccc2f6b448cb309610c4d2f56d93156 Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Wed, 13 May 2015 09:51:25 +0300 Subject: [PATCH 26/28] Remove 'noduplicate' option and react on duplicates unconditionally Only duplicates of singular headers are detected. Header duplication is still checked in Frang classifier for now. --- etc/tfw_frang.conf | 1 - tempesta_fw/classifier/frang.c | 12 ++---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/etc/tfw_frang.conf b/etc/tfw_frang.conf index 53af9f1dd..9e1ce4fd0 100644 --- a/etc/tfw_frang.conf +++ b/etc/tfw_frang.conf @@ -24,7 +24,6 @@ # http_methods [METHOD]...; # http_ct_required true|false; # http_ct_vals ["CONTENT_TYPE"]...; -# http_field_noduplicate true|false; # } # # - options with names *_rate define requests/connections rate per second. diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 572fbbd80..2fa07e862 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -111,7 +111,6 @@ typedef struct { unsigned int http_body_len; bool http_ct_required; bool http_host_required; - bool http_field_noduplicate; /* The bitmask of allowed HTTP Method values. */ unsigned long http_methods_mask; /* The list of allowed Content-Type values. */ @@ -550,10 +549,8 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) __FSM_MOVE(Frang_Req_Hdr_FieldDup); } __FSM_STATE(Frang_Req_Hdr_FieldDup) { - if (frang_cfg.http_field_noduplicate) { - if (req->flags & TFW_HTTP_FIELD_DUPENTRY) { - r = TFW_BLOCK; - } + if (req->flags & TFW_HTTP_FIELD_DUPENTRY) { + r = TFW_BLOCK; } __FSM_MOVE(Frang_Req_Hdr_FieldLenRaw); } @@ -809,11 +806,6 @@ static TfwCfgSpec frang_cfg_section_specs[] = { tfw_cfg_set_bool, &frang_cfg.http_ct_required, }, - { - "http_field_noduplicate", "false", - tfw_cfg_set_bool, - &frang_cfg.http_field_noduplicate, - }, { "http_methods", NULL, frang_set_methods_mask, From 8bf408387989f09a6ceb5d51d993a0c4b818b6aa Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Thu, 14 May 2015 04:09:24 +0300 Subject: [PATCH 27/28] Make checking for a singular header field faster The string to check can be a compound string, but we always have the first character of the header field. Use that to speed up the lookup for a singular header. --- tempesta_fw/http_parser.c | 47 ++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/tempesta_fw/http_parser.c b/tempesta_fw/http_parser.c index a099025c4..b22a6b5d1 100644 --- a/tempesta_fw/http_parser.c +++ b/tempesta_fw/http_parser.c @@ -839,30 +839,47 @@ __parse_transfer_encoding(TfwHttpMsg *msg, unsigned char *data, size_t *lenrval) static bool __header_is_singular(const TfwStr *field) { - int idx; + int idx, ichar, fchar; + bool fplain = !(field->flags & (TFW_STR_COMPOUND|TFW_STR_COMPOUND2)); + #define TfwStr_string(v) { 0, sizeof(v) - 1, (v) } - static const TfwStr field_singular[] = { - TfwStr_string("Host"), - TfwStr_string("Location"), - TfwStr_string("Content_Type"), + /* + * Place strings in alphabetical order to speed up processing. + */ + static const TfwStr field_singular[] __read_mostly = { + TfwStr_string("Authorization"), TfwStr_string("Content_Length"), - TfwStr_string("User_Agent"), - TfwStr_string("Referer"), + TfwStr_string("Content_Type"), TfwStr_string("From"), - TfwStr_string("Authorization"), - TfwStr_string("Proxy_Authorization"), + TfwStr_string("Host"), TfwStr_string("If_Modified_Since"), TfwStr_string("If_Unmodified_Since"), + TfwStr_string("Location"), TfwStr_string("Max_Forwards"), + TfwStr_string("Proxy_Authorization"), + TfwStr_string("Referer"), + TfwStr_string("User_Agent"), }; #undef TfwStr_string - for (idx = 0; idx < ARRAY_SIZE(field_singular); idx++) - if (tfw_str_eq_cstr(field, - field_singular[idx].ptr, - field_singular[idx].len, - TFW_STR_EQ_PREFIX_CASEI)) - return true; + fchar = fplain + ? tolower(*(unsigned char *)field->ptr) + : tolower(*(unsigned char *)((TfwStr *)field->ptr)->ptr); + for (idx = 0; idx < ARRAY_SIZE(field_singular); idx++) { + ichar = tolower(*(unsigned char *)field_singular[idx].ptr); + if (fchar < ichar) + continue; + else if (fchar > ichar) + break; + else if (fchar == ichar) { + const TfwStr *ifield = &field_singular[idx]; + if (fplain && (field->len != ifield->len)) + continue; + if (tfw_str_eq_cstr(field, ifield->ptr, ifield->len, + TFW_STR_EQ_PREFIX_CASEI)) + return true; + } + } return false; } From c67f3a83b4338f31ff53bf7f356eaed61f7c0e2c Mon Sep 17 00:00:00 2001 From: Aleksey Baulin Date: Thu, 14 May 2015 17:43:30 +0300 Subject: [PATCH 28/28] Correctly calculate header and body timeouts Timeout is exceeded when + is less than current value of jiffies. Also, body timeout is measured between body chunks and not from the time the body started coming. --- tempesta_fw/classifier/frang.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tempesta_fw/classifier/frang.c b/tempesta_fw/classifier/frang.c index 2fa07e862..4949fe515 100644 --- a/tempesta_fw/classifier/frang.c +++ b/tempesta_fw/classifier/frang.c @@ -511,7 +511,7 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) && (skb != head_skb) && FSM_HDR_STATE(req->frang_st)) { unsigned long start = req->tm_header; unsigned long delta = frang_cfg.clnt_hdr_timeout; - if (time_is_after_jiffies(start + delta)) + if (time_is_before_jiffies(start + delta)) return TFW_BLOCK; } @@ -604,8 +604,9 @@ frang_http_req_handler(void *obj, unsigned char *data, size_t len) if (frang_cfg.clnt_body_timeout && (skb != head_skb)) { unsigned long start = req->tm_bchunk; unsigned long delta = frang_cfg.clnt_body_timeout; - if (time_is_after_jiffies(start + delta)) + if (time_is_before_jiffies(start + delta)) r = TFW_BLOCK; + req->tm_bchunk = jiffies; } __FSM_MOVE(Frang_Req_Body_Len); }