From 3850b285e290f90ba5849a1a8fdff1436a3c1c57 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 26 Feb 2019 14:34:39 +0500 Subject: [PATCH 01/20] frang: add functions to remove/add hooks in array manner --- tempesta_fw/http_limits.c | 157 +++++++++++++++++++++----------------- 1 file changed, 87 insertions(+), 70 deletions(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index fff5008e22..d2e28991e3 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -236,9 +236,6 @@ typedef struct { FrangRespCodeStat resp_code_stat[FRANG_FREQ]; } FrangAcc; -/* GFSM hooks priorities. */ -int prio0, prio1, prio2, prio3, prio4; - #define FRANG_CLI2ACC(c) ((FrangAcc *)(&(c)->class_prvt)) #define FRANG_ACC2CLI(a) container_of((TfwClassifierPrvt *)a, \ TfwClient, class_prvt) @@ -1144,6 +1141,87 @@ static TfwClassifier frang_class_ops = { * ------------------------------------------------------------------------ */ +typedef struct { + int prio; + int hook_state; + int st0; + unsigned short fsm_id; + const char *name; +} FrangGfsmHook; + +static FrangGfsmHook frang_gfsm_hooks[] = { + { + .prio = -1, + .hook_state = TFW_HTTP_FSM_REQ_MSG, + .fsm_id = TFW_FSM_FRANG_REQ, + .st0 = TFW_FRANG_REQ_FSM_INIT, + .name = "request_msg_end", + }, + { + .prio = -1, + .hook_state = TFW_HTTP_FSM_REQ_CHUNK, + .fsm_id = TFW_FSM_FRANG_REQ, + .st0 = TFW_FRANG_REQ_FSM_INIT, + .name = "request_skb_end", + }, + { + .prio = -1, + .hook_state = TFW_HTTP_FSM_RESP_MSG, + .fsm_id = TFW_FSM_FRANG_RESP, + .st0 = TFW_FRANG_RESP_FSM_INIT, + .name = "response_msg_end", + }, + { + .prio = -1, + .hook_state = TFW_HTTP_FSM_RESP_CHUNK, + .fsm_id = TFW_FSM_FRANG_RESP, + .st0 = TFW_FRANG_RESP_FSM_INIT, + .name = "response_skb_end", + }, + { + .prio = -1, + .hook_state = TFW_HTTP_FSM_RESP_MSG_FWD, + .fsm_id = TFW_FSM_FRANG_RESP, + .st0 = TFW_FRANG_RESP_FSM_INIT, + .name = "response_fwd", + }, +}; + +void +tfw_http_limits_hooks_remove(void) +{ + int i; + + for (i = ARRAY_SIZE(frang_gfsm_hooks) - 1; i >= 0; i--) { + FrangGfsmHook *h = &frang_gfsm_hooks[i]; + if (h->prio == -1) + continue; + tfw_gfsm_unregister_hook(TFW_FSM_HTTP, h->prio, h->hook_state); + h->prio = -1; + } +} + +int +tfw_http_limits_hooks_register(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(frang_gfsm_hooks); i++) { + FrangGfsmHook *h = &frang_gfsm_hooks[i]; + + h->prio = tfw_gfsm_register_hook(TFW_FSM_HTTP, + TFW_GFSM_HOOK_PRIORITY_ANY, + h->hook_state, h->fsm_id, + h->st0); + if (h->prio < 0) { + TFW_ERR_NL("frang: can't register %s hook\n", h->name); + return -EINVAL; + } + } + + return 0; +} + int __init tfw_http_limits_init(void) { @@ -1167,70 +1245,13 @@ tfw_http_limits_init(void) goto err_fsm_resp; } - /* Request hooks. */ - prio0 = tfw_gfsm_register_hook(TFW_FSM_HTTP, - TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_REQ_MSG, - TFW_FSM_FRANG_REQ, - TFW_FRANG_REQ_FSM_INIT); - if (prio0 < 0) { - TFW_ERR_NL("frang: can't register req gfsm msg hook\n"); - r = prio0; - goto err_hook0; - } - prio1 = tfw_gfsm_register_hook(TFW_FSM_HTTP, - TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_REQ_CHUNK, - TFW_FSM_FRANG_REQ, - TFW_FRANG_REQ_FSM_INIT); - if (prio1 < 0) { - TFW_ERR_NL("frang: can't register req gfsm chunk hook\n"); - r = prio1; - goto err_hook1; - } - - /* Response hooks. */ - prio2 = tfw_gfsm_register_hook(TFW_FSM_HTTP, - TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_RESP_MSG, - TFW_FSM_FRANG_RESP, - TFW_FRANG_RESP_FSM_INIT); - if (prio2 < 0) { - TFW_ERR_NL("frang: can't register resp gfsm msg hook\n"); - r = prio2; - goto err_hook2; - } - prio3 = tfw_gfsm_register_hook(TFW_FSM_HTTP, - TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_RESP_CHUNK, - TFW_FSM_FRANG_RESP, - TFW_FRANG_RESP_FSM_INIT); - if (prio3 < 0) { - TFW_ERR_NL("frang: can't register resp gfsm chunk hook\n"); - r = prio3; - goto err_hook3; - } - prio4 = tfw_gfsm_register_hook(TFW_FSM_HTTP, - TFW_GFSM_HOOK_PRIORITY_ANY, - TFW_HTTP_FSM_RESP_MSG_FWD, - TFW_FSM_FRANG_RESP, - TFW_FRANG_RESP_FSM_FWD); - if (prio4 < 0) { - TFW_ERR_NL("frang: can't register resp gfsm msg fwd hook\n"); - r = prio4; - goto err_hook4; - } + r = tfw_http_limits_hooks_register(); + if (r) + goto err_hooks; return 0; -err_hook4: - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio3, TFW_HTTP_FSM_RESP_CHUNK); -err_hook3: - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio2, TFW_HTTP_FSM_RESP_MSG); -err_hook2: - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio1, TFW_HTTP_FSM_REQ_CHUNK); -err_hook1: - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio0, TFW_HTTP_FSM_REQ_MSG); -err_hook0: +err_hooks: + tfw_http_limits_hooks_remove(); tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_RESP); err_fsm_resp: tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_REQ); @@ -1245,11 +1266,7 @@ tfw_http_limits_exit(void) { TFW_DBG("frang exit\n"); - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio4, TFW_HTTP_FSM_RESP_MSG_FWD); - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio3, TFW_HTTP_FSM_RESP_CHUNK); - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio2, TFW_HTTP_FSM_RESP_MSG); - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio1, TFW_HTTP_FSM_REQ_CHUNK); - tfw_gfsm_unregister_hook(TFW_FSM_HTTP, prio0, TFW_HTTP_FSM_REQ_MSG); + tfw_http_limits_hooks_remove(); tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_RESP); tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_REQ); tfw_classifier_unregister(); From 2ff9c4e2456e367bcc7e716a0360a06442d8bd38 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Thu, 25 Jul 2019 17:48:21 +0500 Subject: [PATCH 02/20] Make frang limits working per-vhost and per-location --- tempesta_fw/http_limits.h | 72 +++- tempesta_fw/http_types.h | 12 +- tempesta_fw/vhost.c | 795 +++++++++++++++++++++++--------------- tempesta_fw/vhost.h | 9 +- 4 files changed, 551 insertions(+), 337 deletions(-) diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index 3e6200f725..5a39bf8cb2 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -126,40 +126,72 @@ typedef struct { size_t len; /* The pre-computed strlen(@str). */ } FrangCtVal; -typedef struct frang_cfg_t FrangCfg; - -struct frang_cfg_t { - /* Limits (zero means unlimited). */ +/** + * Global Frang limits. As a request is received, it's not possible to determine + * it's target vhost or/and location until all the headers are parsed. Thus some + * limits can't be redefined for vhost or location and can exist only as + * unique top-level limits. + * + * @clnt_hdr_timeout - Maximum time to receive the full headers set, + * in jiffies; + * @clnt_body_timeout - Maximum time to receive the full body, in jiffies; + * @req_rate - Maximum requests per second over all the + * connections from the single client; + * @req_burst - Allowed request rate burst; + * @conn_rate - Maximum new connections per second from the same + * client; + * @conn_burst - Allowed connection rate burst; + * @conn_max - Maximum number of allowed concurrent connections; + * @http_hchunk_cnt - Maximum number of chunks in header part; + * @http_bchunk_cnt - Maximum number of chunks in body part; + * @ip_block - Block clients by IP address if set, if not - just + * close the client connection. + * + * Zero value means unlimited value. + */ +struct frang_global_cfg_t { + unsigned long clnt_hdr_timeout; + unsigned long clnt_body_timeout; unsigned int req_rate; unsigned int req_burst; unsigned int conn_rate; 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; unsigned int http_hchunk_cnt; unsigned int http_bchunk_cnt; - unsigned int http_hdr_cnt; - bool http_ct_required; - bool http_host_required; bool ip_block; +}; - /* The bitmask of allowed HTTP Method values. */ +/** + * Vhost|location -specific Frang directives. + * + * @http_methods_mask - Allowed HTTP request methods; + * @http_uri_len - Maximum allowed URI len; + * @http_field_len - Maximum HTTP header length; + * @http_body_len - Maximum body size; + * @http_hdr_cnt - Maximum number of headers; + * @http_ct_vals - Allowed 'Content-Type:' values; + * @http_ct_vals_sz - Size of @http_ct_vals member; + * @http_resp_code_block - Response status codes and maximum number of each + * code before client connection is closed. + * @http_ct_required - Header 'Content-Type:' is required; + * @http_host_required - Header 'Host:' is required; + */ +struct frang_vhost_cfg_t { unsigned long http_methods_mask; - /* The list of allowed Content-Type values. */ + unsigned int http_uri_len; + unsigned int http_field_len; + unsigned int http_body_len; + unsigned int http_hdr_cnt; + FrangCtVal *http_ct_vals; + size_t http_ct_vals_sz; FrangHttpRespCodeBlock *http_resp_code_block; + + bool http_ct_required; + bool http_host_required; }; #endif /* __HTTP_LIMITS__ */ diff --git a/tempesta_fw/http_types.h b/tempesta_fw/http_types.h index a9135ad8dd..30ba9b15b0 100644 --- a/tempesta_fw/http_types.h +++ b/tempesta_fw/http_types.h @@ -21,10 +21,12 @@ #define __TFW_HTTP_TYPES_H__ /* Forward declaration of common HTTP types. */ -typedef struct tfw_http_sess_t TfwHttpSess; -typedef struct tfw_http_msg_t TfwHttpMsg; -typedef struct tfw_http_req_t TfwHttpReq; -typedef struct tfw_http_resp_t TfwHttpResp; -typedef struct tfw_vhost_t TfwVhost; +typedef struct tfw_http_sess_t TfwHttpSess; +typedef struct tfw_http_msg_t TfwHttpMsg; +typedef struct tfw_http_req_t TfwHttpReq; +typedef struct tfw_http_resp_t TfwHttpResp; +typedef struct tfw_vhost_t TfwVhost; +typedef struct frang_global_cfg_t FrangGlobCfg; +typedef struct frang_vhost_cfg_t FrangCfg; #endif /* __TFW_HTTP_TYPES_H__ */ diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index db56e0d483..a9b64f9045 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -20,11 +20,11 @@ #include "tempesta_fw.h" #include "hash.h" #include "http.h" +#include "http_limits.h" #include "http_match.h" #include "http_msg.h" #include "vhost.h" #include "str.h" -#include "http_limits.h" #include "client.h" #include "tls_conf.h" @@ -360,6 +360,10 @@ static TfwGlobal tfw_global = { .hdr_via_len = sizeof(s_hdr_via_dflt) - 1, .capuacl = tfw_capuacl_dflt, }; +/* Temporal structures to parse top level (outside vhost) Frang configuration. */ +static FrangCfg tfw_frang_vhost_reconfig; +static FrangGlobCfg tfw_frang_glob_reconfig; + /** * Match vhost to requested name. Called in process context during configuration @@ -1011,6 +1015,38 @@ tfw_location_lookup(TfwVhost *vhost, tfw_match_t op, const char *arg, size_t len return NULL; } +static int +tfw_frang_cfg_inherit(FrangCfg *curr, FrangCfg *from) +{ + int r = 0; + + memcpy(curr, from, sizeof(FrangCfg)); + + if (from->http_ct_vals) { + size_t sz = from->http_ct_vals_sz; + curr->http_ct_vals = kmalloc(sz, GFP_KERNEL); + if (!curr->http_ct_vals) + r = -ENOMEM; + else + memcpy(curr->http_ct_vals, from->http_ct_vals, sz); + } + if (!r && from->http_resp_code_block) { + size_t sz = sizeof(FrangHttpRespCodeBlock); + curr->http_resp_code_block = kmalloc(sz, GFP_KERNEL); + if (!curr->http_resp_code_block) { + r = -ENOMEM; + } + else { + memcpy(curr->http_resp_code_block, + from->http_resp_code_block, sz); + } + } + if (unlikely(r)) + T_WARN_NL("Failed to inherit Frang limits: %d.", r); + + return r; +} + static int tfw_location_init(TfwLocation *loc, tfw_match_t op, const char *arg, size_t len, TfwPool *pool) @@ -1060,6 +1096,10 @@ tfw_location_new(TfwVhost *vhost, tfw_match_t op, const char *arg, size_t len) if (tfw_location_init(loc, op, arg, len, vhost->hdrs_pool)) return NULL; vhost->loc_sz++; + + if (tfw_frang_cfg_inherit(loc->frang_cfg, vhost->loc_dflt->frang_cfg)) + return NULL; + return loc; } @@ -1175,6 +1215,26 @@ tfw_cfgop_out_location_finish(TfwCfgSpec *cs) return 0; } +static void +__tfw_frang_clean(FrangCfg *cfg) +{ + kfree(cfg->http_ct_vals); + kfree(cfg->http_resp_code_block); +} + +static void +tfw_frang_clean(FrangCfg *cfg) +{ + __tfw_frang_clean(cfg); + memset(cfg, 0, sizeof(FrangCfg)); +} + +static void +tfw_frang_global_clean(FrangGlobCfg *cfg) +{ + memset(cfg, 0, sizeof(FrangGlobCfg)); +} + /* * Free 'location' memory which has been allocated while processing * configuration directives. @@ -1196,9 +1256,7 @@ tfw_location_del(TfwLocation *loc) kfree(loc->nipdef[i]); } - kfree(loc->frang_cfg->http_ct_vals); - kfree(loc->frang_cfg->http_resp_code_block); - + __tfw_frang_clean(loc->frang_cfg); /* * Free loc->arg and loc->frang_cfg, loc->capo, * loc->nipdef and loc->mod_hdrs. @@ -1440,6 +1498,8 @@ tfw_vhost_destroy(TfwVhost *vhost) { int i; + T_DBG2("destroy vhost '%s'\n", vhost->name); + for (i = 0; i < vhost->loc_sz; ++i) tfw_location_del(&vhost->loc[i]); tfw_location_del(vhost->loc_dflt); @@ -1473,7 +1533,8 @@ tfw_vhost_create(const char *name) vhost->name.len = name_sz - 1; vhost->loc_dflt = (TfwLocation *)(vhost->name.data + name_sz); vhost->loc = (TfwLocation *)(vhost->loc_dflt + 1); - vhost->tls_cfg.priv = vhost->loc + TFW_LOCATION_ARRAY_SZ; + vhost->frang_gconf = (FrangGlobCfg *)(vhost->loc + TFW_LOCATION_ARRAY_SZ); + vhost->tls_cfg.priv = (vhost->frang_gconf + 1); memcpy(vhost->name.data, name, name_sz); vhost->hdrs_pool = pool; atomic64_set(&vhost->refcnt, 1); @@ -1494,11 +1555,20 @@ tfw_vhost_new(const char *name) TFW_HTTP_MATCH_O_WILDCARD, "*", 1, vhost->hdrs_pool)) { - TFW_ERR_NL("Unable to add default location" - " for vhost '%s'.\n", name); + T_ERR_NL("Unable to add default location for vhost '%s'.\n", + name); tfw_vhost_destroy(vhost); return NULL; } + if (strcasecmp(name, TFW_VH_DFT_NAME)) { + TfwVhost *dvh = tfw_vhosts_reconfig->vhost_dflt; + if (tfw_frang_cfg_inherit(vhost->loc_dflt->frang_cfg, + dvh->loc_dflt->frang_cfg)) + { + tfw_vhost_destroy(vhost); + return NULL; + } + } return vhost; } @@ -1516,14 +1586,6 @@ tfw_vhost_add(TfwVhost *vhost) } } -/* - * Frang configuration for global and location-specific settings. - * Note, global Frang settings are not reconfigurable. - */ - -/* Frang global settings object. */ -static FrangCfg frang_cfg __read_mostly; - static const TfwCfgEnum frang_http_methods_enum[] = { { "copy", TFW_HTTP_METH_COPY }, { "delete", TFW_HTTP_METH_DELETE }, @@ -1544,18 +1606,11 @@ static const TfwCfgEnum frang_http_methods_enum[] = { {} }; -/* Return Frang global configuration settings. */ -FrangCfg * -tfw_vhost_global_frang_cfg(void) -{ - return &frang_cfg; -} - static int -tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce, +__tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce, unsigned long *cfg_methods_mask) { - int i, r, method_id; + int i, method_id; const char *method_str; unsigned long methods_mask = 0; @@ -1563,8 +1618,8 @@ tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce, < _TFW_HTTP_METH_COUNT); TFW_CFG_ENTRY_FOR_EACH_VAL(ce, i, method_str) { - r = tfw_cfg_map_enum(frang_http_methods_enum, method_str, - &method_id); + int r = tfw_cfg_map_enum(frang_http_methods_enum, method_str, + &method_id); if (r) { TFW_ERR_NL("frang: invalid method: '%s'\n", method_str); return -EINVAL; @@ -1581,7 +1636,7 @@ tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce, } static int -tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) +__tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) { void *mem; const char *in_str; @@ -1634,6 +1689,7 @@ tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) BUG_ON(strs_pos != (strs + strs_size)); conf->http_ct_vals = vals; + conf->http_ct_vals_sz = vals_size + strs_size; return 0; } @@ -1656,7 +1712,8 @@ frang_parse_ushort(const char *s, unsigned short *out) * Save response code block configuration */ static int -tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) +__tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, + FrangCfg *conf) { FrangHttpRespCodeBlock *cb; static const char *error_msg_begin = "frang: http_resp_code_block:"; @@ -1705,178 +1762,202 @@ tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) } static int -tfw_cfgop_frang_loc_req_rate(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_glob_in_vhost(TfwCfgSpec *cs, TfwCfgEntry *ce) { - int r; - - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->req_rate; - r = tfw_cfg_set_int(cs, ce); - cs->dest = NULL; - return r; + T_ERR_NL("Directive '%s' from 'frang_limits' group can be used " + "only as top-level directive (outside of any 'vhost' " + "directive).\n", + cs->name); + return -EINVAL; } static int -tfw_cfgop_frang_loc_req_burst(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_glob_set_bool(TfwCfgSpec *cs, TfwCfgEntry *ce) { - int r; - - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->req_burst; - r = tfw_cfg_set_int(cs, ce); - cs->dest = NULL; - return r; + /* + * 'frang_limits' section may appear multiple times to modify defaults + * values for future 'frang_limits' directives. + */ + if (ce->dflt_value && *(bool *)(cs->dest)) + return 0; + return tfw_cfg_set_bool(cs, ce); } static int -tfw_cfgop_frang_loc_hdr_timeout(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_glob_set_int(TfwCfgSpec *cs, TfwCfgEntry *ce) { - int r; - - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->clnt_hdr_timeout; - r = tfw_cfg_set_int(cs, ce); - cs->dest = NULL; - return r; + if (ce->dflt_value && *(unsigned int *)(cs->dest)) + return 0; + return tfw_cfg_set_int(cs, ce); } static int -tfw_cfgop_frang_loc_body_timeout(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_hdr_timeout(TfwCfgSpec *cs, TfwCfgEntry *ce) { + unsigned int secs; int r; - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->clnt_body_timeout; + if (ce->dflt_value && tfw_frang_glob_reconfig.clnt_hdr_timeout) + return 0; + cs->dest = &secs; r = tfw_cfg_set_int(cs, ce); cs->dest = NULL; + if (!r) + tfw_frang_glob_reconfig.clnt_hdr_timeout = (unsigned long) HZ + * secs; + return r; } static int -tfw_cfgop_frang_loc_uri_len(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_body_timeout(TfwCfgSpec *cs, TfwCfgEntry *ce) { + unsigned int secs; int r; - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_uri_len; + if (ce->dflt_value && tfw_frang_glob_reconfig.clnt_body_timeout) + return 0; + cs->dest = &secs; r = tfw_cfg_set_int(cs, ce); cs->dest = NULL; + if (!r) + tfw_frang_glob_reconfig.clnt_body_timeout = (unsigned long) HZ + * secs; + return r; } -static int -tfw_cfgop_frang_loc_field_len(TfwCfgSpec *cs, TfwCfgEntry *ce) +static FrangCfg * +tfw_cfgop_frang_get_cfg(void) { - int r; - - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_field_len; - r = tfw_cfg_set_int(cs, ce); - cs->dest = NULL; - return r; + if (tfwcfg_this_location) + return tfwcfg_this_location->frang_cfg; + if (tfw_vhost_entry) + return tfw_vhost_entry->loc_dflt->frang_cfg; + return &tfw_frang_vhost_reconfig; } static int -tfw_cfgop_frang_loc_body_len(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_uri_len(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_body_len; + if (ce->dflt_value && cfg->http_uri_len) + return 0; + cs->dest = &cfg->http_uri_len; r = tfw_cfg_set_int(cs, ce); cs->dest = NULL; return r; } static int -tfw_cfgop_frang_loc_hdr_cnt(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_field_len(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_hdr_cnt; + if (ce->dflt_value && cfg->http_field_len) + return 0; + cs->dest = &cfg->http_field_len; r = tfw_cfg_set_int(cs, ce); cs->dest = NULL; return r; } static int -tfw_cfgop_frang_loc_hdr_chunk_cnt(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_body_len(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_hchunk_cnt; + if (ce->dflt_value && cfg->http_body_len) + return 0; + cs->dest = &cfg->http_body_len; r = tfw_cfg_set_int(cs, ce); cs->dest = NULL; return r; } static int -tfw_cfgop_frang_loc_body_chunk_cnt(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_hdr_cnt(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_bchunk_cnt; + if (ce->dflt_value && cfg->http_hdr_cnt) + return 0; + cs->dest = &cfg->http_hdr_cnt; r = tfw_cfg_set_int(cs, ce); cs->dest = NULL; return r; } static int -tfw_cfgop_frang_loc_host_required(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_host_required(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_host_required; + if (ce->dflt_value && cfg->http_host_required) + return 0; + cs->dest = &cfg->http_host_required; r = tfw_cfg_set_bool(cs, ce); cs->dest = NULL; return r; } static int -tfw_cfgop_frang_loc_ct_required(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_ct_required(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - cs->dest = &tfwcfg_this_location->frang_cfg->http_ct_required; + if (ce->dflt_value && cfg->http_ct_required) + return 0; + cs->dest = &cfg->http_ct_required; r = tfw_cfg_set_bool(cs, ce); cs->dest = NULL; return r; } static int -tfw_cfgop_frang_loc_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce) { - unsigned long *methods_mask; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); - BUG_ON(!tfwcfg_this_location); - methods_mask = &tfwcfg_this_location->frang_cfg->http_methods_mask; - return tfw_cfgop_frang_http_methods(cs, ce, methods_mask); + if (ce->dflt_value && cfg->http_methods_mask) + return 0; + return __tfw_cfgop_frang_http_methods(cs, ce, &cfg->http_methods_mask); } static int -tfw_cfgop_frang_out_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) { - return tfw_cfgop_frang_http_methods(cs, ce, - &frang_cfg.http_methods_mask); -} + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); -static int -tfw_cfgop_frang_loc_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) -{ - BUG_ON(!tfwcfg_this_location); - return tfw_cfgop_frang_http_ct_vals(cs, ce, - tfwcfg_this_location->frang_cfg); + if (cfg->http_ct_vals) { + if (ce->dflt_value) + return 0; + kfree(cfg->http_ct_vals); + cfg->http_ct_vals = NULL; + cfg->http_ct_vals_sz = 0; + } + return __tfw_cfgop_frang_http_ct_vals(cs, ce, cfg); } static int -tfw_cfgop_frang_out_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) +tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce) { - return tfw_cfgop_frang_http_ct_vals(cs, ce, &frang_cfg); + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + + if (cfg->http_resp_code_block) { + if (ce->dflt_value) + return 0; + kfree(cfg->http_resp_code_block); + cfg->http_resp_code_block = NULL; + } + return __tfw_cfgop_frang_rsp_code_block(cs, ce, cfg); } static int @@ -1886,18 +1967,15 @@ tfw_cfgop_http_post_validate(TfwCfgSpec *cs, TfwCfgEntry *ce) return 0; } -static int -tfw_cfgop_frang_loc_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce) -{ - BUG_ON(!tfwcfg_this_location); - return tfw_cfgop_frang_rsp_code_block(cs, ce, - tfwcfg_this_location->frang_cfg); -} - -static int -tfw_cfgop_frang_out_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce) +/* + * Frang objects are cleaned when its location is destroyed. This dummy function + * is required to save time during reconfiguration by skipping traversing over + * the list of child directives cleanup functions. + */ +static void +tfw_cfgop_frang_cleanup(TfwCfgSpec *cs) { - return tfw_cfgop_frang_rsp_code_block(cs, ce, &frang_cfg); + return; } static int @@ -2007,6 +2085,8 @@ tfw_vhost_cfgstart(void) } tfw_vhosts_reconfig->vhost_dflt = vh_dflt; + tfw_frang_clean(&tfw_frang_vhost_reconfig); + tfw_frang_global_clean(&tfw_frang_glob_reconfig); return 0; } @@ -2018,6 +2098,7 @@ tfw_vhost_cfgend(void) TfwVhost *vh_dflt; int r; + *tfw_vhosts_reconfig->vhost_dflt->frang_gconf = tfw_frang_glob_reconfig; /* * Add default vhost into list if it hadn't been added yet explicitly * to keep default location policies. @@ -2031,6 +2112,9 @@ tfw_vhost_cfgend(void) * request is parsed and assigned to any location. */ vh_dflt = tfw_vhosts_reconfig->vhost_dflt; + if (tfw_frang_cfg_inherit(vh_dflt->loc_dflt->frang_cfg, + &tfw_frang_vhost_reconfig)) + return -ENOMEM; sg_def = tfw_sg_lookup_reconfig(TFW_VH_DFT_NAME, SLEN("default")); vh_dflt->loc_dflt->main_sg = sg_def; tfw_vhost_add(vh_dflt); @@ -2038,9 +2122,9 @@ tfw_vhost_cfgend(void) return r; if (tfw_global.cache_purge && !tfw_global.cache_purge_acl) - TFW_WARN_NL("Directives mismatching: cache_purge directive" - " works only in combination with cache_purge_acl" - " directive.\n"); + T_WARN_NL("Directives mismatching: 'cache_purge' directive " + "requires 'cache_purge_acl', but it wasn't " + "provided. 'cache_purge' directive is ignored.\n"); return 0; } @@ -2071,6 +2155,9 @@ tfw_cfgop_vhost_begin(TfwCfgSpec *cs, TfwCfgEntry *ce) if (__tfw_cfgop_proxy_pass(TFW_VH_DFT_NAME, NULL, tfw_vhost_entry->loc_dflt)) return -EINVAL; + if (tfw_frang_cfg_inherit(tfw_vhost_entry->loc_dflt->frang_cfg, + &tfw_frang_vhost_reconfig)) + return -ENOMEM; } else { if (!(tfw_vhost_entry = tfw_vhost_new(ce->vals[0]))) { TFW_ERR_NL("Unable to create new vhost entry: '%s'\n", @@ -2178,10 +2265,6 @@ tfw_vhost_cfgclean(void) if (tfw_runstate_is_reconfig()) return; - kfree(frang_cfg.http_ct_vals); - kfree(frang_cfg.http_resp_code_block); - memset(&frang_cfg, 0, sizeof(frang_cfg)); - tfw_global.capuacl_sz = tfw_global.cache_purge = tfw_global.cache_purge_mode = @@ -2192,159 +2275,350 @@ tfw_vhost_cfgclean(void) tfw_global.hdr_via = s_hdr_via_dflt; } -static TfwCfgSpec tfw_vhost_location_specs[] = { +/* + * Not all Frang specs can be applied to nested locations and can be applied + * only as high-level options. It's possible to provide it's own sets for global + * and inner (location) options. But warning "line 32: the frang limit can be + * assigned only at global level" is much more user friendly than generic + * "line 32: unknown command". + */ +static TfwCfgSpec tfw_global_frang_specs[] = { + /* Options that can be enabled|disabled only globally. */ { - .name = "cache_bypass", - .deflt = NULL, - .handler = tfw_cfgop_loc_cache_bypass, - .allow_none = true, - .allow_repeat = true, + .name = "ip_block", + .deflt = "off", + .handler = tfw_cfgop_frang_glob_set_bool, + .dest = &tfw_frang_glob_reconfig.ip_block, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, .allow_reconfig = true, }, { - .name = "cache_fulfill", - .deflt = NULL, - .handler = tfw_cfgop_loc_cache_fulfill, - .allow_none = true, - .allow_repeat = true, + .name = "request_rate", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.req_rate, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, .allow_reconfig = true, }, { - .name = "nonidempotent", - .deflt = NULL, - .handler = tfw_cfgop_loc_nonidempotent, - .allow_none = true, - .allow_repeat = true, + .name = "request_burst", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.req_burst, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, .allow_reconfig = true, }, { - .name = "req_hdr_add", - .deflt = NULL, - .handler = tfw_cfgop_loc_req_hdr_add, - .allow_none = true, - .allow_repeat = true, + .name = "connection_rate", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.conn_rate, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, .allow_reconfig = true, }, { - .name = "req_hdr_set", - .deflt = NULL, - .handler = tfw_cfgop_loc_req_hdr_set, - .allow_none = true, - .allow_repeat = true, + .name = "connection_burst", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.conn_burst, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, .allow_reconfig = true, }, { - .name = "resp_hdr_add", + .name = "concurrent_connections", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.conn_max, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, + .allow_reconfig = true, + }, + { + .name = "client_header_timeout", + .deflt = "0", + .handler = tfw_cfgop_frang_hdr_timeout, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, + .allow_reconfig = true, + }, + { + .name = "client_body_timeout", + .deflt = "0", + .handler = tfw_cfgop_frang_body_timeout, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, + .allow_reconfig = true, + }, + { + .name = "http_header_chunk_cnt", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.http_hchunk_cnt, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, + .allow_reconfig = true, + }, + { + .name = "http_body_chunk_cnt", + .deflt = "0", + .handler = tfw_cfgop_frang_glob_set_int, + .dest = &tfw_frang_glob_reconfig.http_bchunk_cnt, + .spec_ext = &(TfwCfgSpecInt) { + .range = { 0, INT_MAX }, + }, + .allow_reconfig = true, + }, + /* Option can be redefined per vhost|location. */ + { + .name = "http_uri_len", + .deflt = "0", + .handler = tfw_cfgop_frang_uri_len, + .allow_reconfig = true, + }, + { + .name = "http_field_len", + .deflt = "0", + .handler = tfw_cfgop_frang_field_len, + .allow_reconfig = true, + }, + { + .name = "http_body_len", + .deflt = "1073741824", /* 1 Gb. */ + .handler = tfw_cfgop_frang_body_len, + .allow_reconfig = true, + }, + { + .name = "http_header_cnt", + .deflt = "0", + .handler = tfw_cfgop_frang_hdr_cnt, + .allow_reconfig = true, + }, + { + .name = "http_host_required", + .deflt = "true", + .handler = tfw_cfgop_frang_host_required, + .allow_reconfig = true, + }, + { + .name = "http_ct_required", + .deflt = "false", + .handler = tfw_cfgop_frang_ct_required, + .allow_reconfig = true, + }, + { + .name = "http_methods", + .deflt = "", + .handler = tfw_cfgop_frang_http_methods, + .allow_reconfig = true, + }, + { + .name = "http_ct_vals", .deflt = NULL, - .handler = tfw_cfgop_loc_resp_hdr_add, + .handler = tfw_cfgop_frang_http_ct_vals, .allow_none = true, - .allow_repeat = true, .allow_reconfig = true, }, { - .name = "resp_hdr_set", + .name = "http_resp_code_block", .deflt = NULL, - .handler = tfw_cfgop_loc_resp_hdr_set, + .handler = tfw_cfgop_frang_rsp_code_block, .allow_none = true, - .allow_repeat = true, .allow_reconfig = true, }, + { 0 } +}; + +static TfwCfgSpec tfw_vhost_frang_specs[] = { + /* Options that can be enabled|disabled only globally. */ { - .name = "request_rate", - .handler = tfw_cfgop_frang_loc_req_rate, + .name = "ip_block", + .handler = tfw_cfgop_frang_glob_in_vhost, + .allow_reconfig = true, .allow_none = true, - .allow_repeat = false, + }, + { + .name = "request_rate", + .handler = tfw_cfgop_frang_glob_in_vhost, .allow_reconfig = true, + .allow_none = true, }, { .name = "request_burst", - .handler = tfw_cfgop_frang_loc_req_burst, + .handler = tfw_cfgop_frang_glob_in_vhost, + .allow_reconfig = true, .allow_none = true, - .allow_repeat = false, + }, + { + .name = "connection_rate", + .handler = tfw_cfgop_frang_glob_in_vhost, .allow_reconfig = true, + .allow_none = true, }, { - .name = "client_header_timeout", - .handler = tfw_cfgop_frang_loc_hdr_timeout, + .name = "connection_burst", + .handler = tfw_cfgop_frang_glob_in_vhost, + .allow_reconfig = true, .allow_none = true, - .allow_repeat = false, + }, + { + .name = "concurrent_connections", + .handler = tfw_cfgop_frang_glob_in_vhost, .allow_reconfig = true, + .allow_none = true, + }, + { + .name = "client_header_timeout", + .handler = tfw_cfgop_frang_glob_in_vhost, + .allow_reconfig = true, + .allow_none = true, }, { .name = "client_body_timeout", - .handler = tfw_cfgop_frang_loc_body_timeout, + .handler = tfw_cfgop_frang_glob_in_vhost, + .allow_reconfig = true, .allow_none = true, - .allow_repeat = false, + }, + { + .name = "http_header_chunk_cnt", + .handler = tfw_cfgop_frang_glob_in_vhost, .allow_reconfig = true, + .allow_none = true, }, { - .name = "http_uri_len", - .handler = tfw_cfgop_frang_loc_uri_len, + .name = "http_body_chunk_cnt", + .handler = tfw_cfgop_frang_glob_in_vhost, + .allow_reconfig = true, .allow_none = true, - .allow_repeat = false, + }, + /* Option can be redefined per vhost|location. */ + { + .name = "http_uri_len", + .deflt = "0", + .handler = tfw_cfgop_frang_uri_len, .allow_reconfig = true, }, { .name = "http_field_len", - .handler = tfw_cfgop_frang_loc_field_len, - .allow_none = true, - .allow_repeat = false, + .deflt = "0", + .handler = tfw_cfgop_frang_field_len, .allow_reconfig = true, }, { .name = "http_body_len", - .handler = tfw_cfgop_frang_loc_body_len, - .allow_none = true, - .allow_repeat = false, + .deflt = "1073741824", /* 1 Gb. */ + .handler = tfw_cfgop_frang_body_len, .allow_reconfig = true, }, { .name = "http_header_cnt", - .handler = tfw_cfgop_frang_loc_hdr_cnt, + .deflt = "0", + .handler = tfw_cfgop_frang_hdr_cnt, + .allow_reconfig = true, + }, + { + .name = "http_host_required", + .deflt = "true", + .handler = tfw_cfgop_frang_host_required, + .allow_reconfig = true, + }, + { + .name = "http_ct_required", + .deflt = "false", + .handler = tfw_cfgop_frang_ct_required, + .allow_reconfig = true, + }, + { + .name = "http_methods", + .deflt = "", + .handler = tfw_cfgop_frang_http_methods, + .allow_reconfig = true, + }, + { + .name = "http_ct_vals", + .deflt = NULL, + .handler = tfw_cfgop_frang_http_ct_vals, .allow_none = true, - .allow_repeat = false, .allow_reconfig = true, }, { - .name = "http_header_chunk_cnt", - .handler = tfw_cfgop_frang_loc_hdr_chunk_cnt, + .name = "http_resp_code_block", + .deflt = NULL, + .handler = tfw_cfgop_frang_rsp_code_block, .allow_none = true, - .allow_repeat = false, .allow_reconfig = true, }, + { 0 } +}; + +static TfwCfgSpec tfw_vhost_location_specs[] = { { - .name = "http_body_chunk_cnt", - .handler = tfw_cfgop_frang_loc_body_chunk_cnt, + .name = "cache_bypass", + .deflt = NULL, + .handler = tfw_cfgop_loc_cache_bypass, .allow_none = true, - .allow_repeat = false, + .allow_repeat = true, .allow_reconfig = true, }, { - .name = "http_host_required", - .handler = tfw_cfgop_frang_loc_host_required, + .name = "cache_fulfill", + .deflt = NULL, + .handler = tfw_cfgop_loc_cache_fulfill, .allow_none = true, - .allow_repeat = false, + .allow_repeat = true, .allow_reconfig = true, }, { - .name = "http_ct_required", - .handler = tfw_cfgop_frang_loc_ct_required, + .name = "nonidempotent", + .deflt = NULL, + .handler = tfw_cfgop_loc_nonidempotent, .allow_none = true, - .allow_repeat = false, + .allow_repeat = true, .allow_reconfig = true, }, { - .name = "http_methods", - .handler = tfw_cfgop_frang_loc_http_methods, + .name = "req_hdr_add", + .deflt = NULL, + .handler = tfw_cfgop_loc_req_hdr_add, .allow_none = true, - .allow_repeat = false, + .allow_repeat = true, .allow_reconfig = true, }, { - .name = "http_ct_vals", - .handler = tfw_cfgop_frang_loc_http_ct_vals, + .name = "req_hdr_set", + .deflt = NULL, + .handler = tfw_cfgop_loc_req_hdr_set, .allow_none = true, - .allow_repeat = false, + .allow_repeat = true, + .allow_reconfig = true, + }, + { + .name = "resp_hdr_add", + .deflt = NULL, + .handler = tfw_cfgop_loc_resp_hdr_add, + .allow_none = true, + .allow_repeat = true, + .allow_reconfig = true, + }, + { + .name = "resp_hdr_set", + .deflt = NULL, + .handler = tfw_cfgop_loc_resp_hdr_set, + .allow_none = true, + .allow_repeat = true, .allow_reconfig = true, }, { @@ -2356,7 +2630,7 @@ static TfwCfgSpec tfw_vhost_location_specs[] = { }, { .name = "http_resp_code_block", - .handler = tfw_cfgop_frang_loc_rsp_code_block, + .handler = tfw_cfgop_frang_rsp_code_block, .allow_none = true, .allow_repeat = false, .allow_reconfig = true, @@ -2369,6 +2643,15 @@ static TfwCfgSpec tfw_vhost_location_specs[] = { .allow_repeat = false, .allow_reconfig = true, }, + { + .name = "frang_limits", + .handler = tfw_cfg_handle_children, + .cleanup = tfw_cfgop_frang_cleanup, + .dest = tfw_vhost_frang_specs, + .allow_none = true, + .allow_repeat = false, + .allow_reconfig = true, + }, { 0 } }; @@ -2482,122 +2765,14 @@ static TfwCfgSpec tfw_vhost_internal_specs[] = { .allow_repeat = true, .allow_reconfig = true, }, - { 0 } -}; - -static TfwCfgSpec tfw_vhost_frang_limits_specs[] = { - { - .name = "ip_block", - .deflt = "off", - .handler = tfw_cfg_set_bool, - .dest = &frang_cfg.ip_block, - }, - { - .name = "request_rate", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.req_rate, - }, - { - .name = "request_burst", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.req_burst, - }, - { - .name = "connection_rate", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.conn_rate, - }, - { - .name = "connection_burst", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.conn_burst, - }, - { - .name = "concurrent_connections", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.conn_max, - }, - { - .name = "client_header_timeout", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = (unsigned int *)&frang_cfg.clnt_hdr_timeout, - }, { - .name = "client_body_timeout", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = (unsigned int *)&frang_cfg.clnt_body_timeout, - }, - { - .name = "http_uri_len", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.http_uri_len, - }, - { - .name = "http_field_len", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.http_field_len, - }, - { - .name = "http_body_len", - .deflt = "1073741824", /* 1 Gb. */ - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.http_body_len, - }, - { - .name = "http_header_cnt", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.http_hdr_cnt, - }, - { - .name = "http_header_chunk_cnt", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.http_hchunk_cnt, - }, - { - .name = "http_body_chunk_cnt", - .deflt = "0", - .handler = tfw_cfg_set_int, - .dest = &frang_cfg.http_bchunk_cnt, - }, - { - .name = "http_host_required", - .deflt = "true", - .handler = tfw_cfg_set_bool, - .dest = &frang_cfg.http_host_required, - }, - { - .name = "http_ct_required", - .deflt = "false", - .handler = tfw_cfg_set_bool, - .dest = &frang_cfg.http_ct_required, - }, - { - .name = "http_methods", - .deflt = "", - .handler = tfw_cfgop_frang_out_http_methods, - }, - { - .name = "http_ct_vals", - .deflt = NULL, - .handler = tfw_cfgop_frang_out_http_ct_vals, - .allow_none = true, - }, - { - .name = "http_resp_code_block", - .deflt = NULL, - .handler = tfw_cfgop_frang_out_rsp_code_block, + .name = "frang_limits", + .handler = tfw_cfg_handle_children, + .cleanup = tfw_cfgop_frang_cleanup, + .dest = tfw_vhost_frang_specs, .allow_none = true, + .allow_repeat = true, + .allow_reconfig = true, }, { 0 } }; @@ -2745,11 +2920,11 @@ static TfwCfgSpec tfw_vhost_specs[] = { { .name = "frang_limits", .handler = tfw_cfg_handle_children, - .cleanup = tfw_cfg_cleanup_children, - .dest = tfw_vhost_frang_limits_specs, + .cleanup = tfw_cfgop_frang_cleanup, + .dest = tfw_global_frang_specs, .allow_none = true, - .allow_repeat = false, - .allow_reconfig = false, + .allow_repeat = true, + .allow_reconfig = true, }, { 0 } }; diff --git a/tempesta_fw/vhost.h b/tempesta_fw/vhost.h index 9f7c91e78d..9dd17ca9dc 100644 --- a/tempesta_fw/vhost.h +++ b/tempesta_fw/vhost.h @@ -116,7 +116,7 @@ typedef struct { size_t nipdef_sz; TfwCaPolicy **capo; TfwNipDef **nipdef; - struct frang_cfg_t *frang_cfg; + FrangCfg *frang_cfg; TfwSrvGroup *main_sg; TfwSrvGroup *backup_sg; TfwPool *hdrs_pool; @@ -144,6 +144,10 @@ enum { * @loc_dflt - Default policy. * @vhost_dflt - Pointer to default virtual host with global policies. * @hdrs_pool - Modification headers allocation pool for vhost's policies. + * @frang_gconf - Global frang configuration. Applicable only for 'default' + * vhost and NULL for others. Provides frang configuration + * options used before request is parsed and assigned to any + * vhost. * @refcnt - Number of users of the virtual host object. * @loc_sz - Count of elements in @loc array. * @flags - flags. @@ -156,6 +160,7 @@ struct tfw_vhost_t { TfwLocation *loc_dflt; TfwVhost *vhost_dflt; TfwPool *hdrs_pool; + FrangGlobCfg *frang_gconf; atomic64_t refcnt; size_t loc_sz; unsigned long flags; @@ -214,9 +219,9 @@ bool tfw_vhost_is_default_reconfig(TfwVhost *vhost); TfwSrvConn *tfw_vhost_get_srv_conn(TfwMsg *msg); TfwVhost *tfw_vhost_new(const char *name); TfwGlobal *tfw_vhost_get_global(void); +TfwLocation *tfw_vhost_get_global_location(void); TfwHdrMods *tfw_vhost_get_hdr_mods(TfwLocation *loc, TfwVhost *vhost, int mod_type); -struct frang_cfg_t *tfw_vhost_global_frang_cfg(void); static inline TfwVhost* tfw_vhost_from_tls_conf(const TlsPeerCfg *cfg) From 191310b2d20400e257723917e99fd0b2466049ea Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Thu, 25 Jul 2019 17:53:23 +0500 Subject: [PATCH 03/20] Fix frang limits to work per vhost and per location --- tempesta_fw/client.h | 1 + tempesta_fw/http.h | 4 + tempesta_fw/http_limits.c | 570 ++++++++++++++++++++++---------------- tempesta_fw/http_parser.c | 20 ++ tempesta_fw/http_parser.h | 1 + tempesta_fw/sock_clnt.c | 14 + tempesta_fw/str.h | 2 + tempesta_fw/vhost.c | 10 - tempesta_fw/vhost.h | 1 - 9 files changed, 366 insertions(+), 257 deletions(-) diff --git a/tempesta_fw/client.h b/tempesta_fw/client.h index f75b4f59a1..cd46546103 100644 --- a/tempesta_fw/client.h +++ b/tempesta_fw/client.h @@ -43,5 +43,6 @@ int tfw_client_for_each(int (*fn)(void *)); void tfw_client_set_expires_time(unsigned int expires_time); void tfw_cli_conn_release(TfwCliConn *cli_conn); int tfw_cli_conn_send(TfwCliConn *cli_conn, TfwMsg *msg); +int tfw_cli_conn_close_all_sync(TfwClient *cli); #endif /* __TFW_CLIENT_H__ */ diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index 15caaaf0ae..3e2ad75bfb 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -230,6 +230,8 @@ enum { TFW_HTTP_B_CONN_EXTRA, /* Chunked transfer encoding. */ TFW_HTTP_B_CHUNKED, + /* Message has chunked trailer headers part. */ + TFW_HTTP_B_CHUNKED_TRAILER, /* The message body is limited by the connection closing. */ TFW_HTTP_B_UNLIMITED, /* Media type is multipart/form-data. */ @@ -238,6 +240,8 @@ enum { TFW_HTTP_B_CT_MULTIPART_HAS_BOUNDARY, /* Singular header presents more than once. */ TFW_HTTP_B_FIELD_DUPENTRY, + /* Message is fully parsed */ + TFW_HTTP_B_FULLY_PARSED, /* Request flags. */ TFW_HTTP_FLAGS_REQ, diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index d2e28991e3..7b9eff2f92 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -239,16 +239,6 @@ typedef struct { #define FRANG_CLI2ACC(c) ((FrangAcc *)(&(c)->class_prvt)) #define FRANG_ACC2CLI(a) container_of((TfwClassifierPrvt *)a, \ TfwClient, class_prvt) -/* - * Get frang configuration variable from request's location; - * if variable is zero or request has no location, use global - * frang configuration. - */ -#define __FRANG_CFG_VAR(name, member) \ - const typeof(((FrangCfg *)0)->member) name = \ - (req->location && req->location->frang_cfg->member \ - ? req->location->frang_cfg->member \ - : tfw_vhost_global_frang_cfg()->member) #define frang_msg(check, addr, fmt, ...) \ TFW_WARN_MOD_ADDR(frang, check, addr, TFW_NO_PORT, fmt, ##__VA_ARGS__) @@ -281,7 +271,7 @@ do { \ #endif static int -frang_conn_limit(FrangAcc *ra, FrangCfg *conf) +frang_conn_limit(FrangAcc *ra, FrangGlobCfg *conf) { unsigned long ts = (jiffies * FRANG_FREQ) / HZ; unsigned int csum = 0; @@ -341,16 +331,25 @@ __frang_init_acc(void *data) static int frang_conn_new(struct sock *sk) { - int r; + int r = TFW_BLOCK; FrangAcc *ra; TfwClient *cli; - FrangCfg *conf = tfw_vhost_global_frang_cfg(); TfwAddr addr; + TfwVhost *dflt_vh = tfw_vhost_lookup_default(); + + /* + * On reboot under heavy load global vhost may be not ready yet. + * Don't pass such connections, force client to reconnect later, when + * we will be ready. + */ + if (unlikely(!dflt_vh)) + return TFW_BLOCK; ss_getpeername(sk, &addr); cli = tfw_client_obtain(addr, NULL, NULL, __frang_init_acc); if (unlikely(!cli)) { TFW_ERR("can't obtain a client for frang accounting\n"); + tfw_vhost_put(dflt_vh); return TFW_BLOCK; } @@ -370,13 +369,14 @@ frang_conn_new(struct sock *sk) */ sk->sk_security = ra; - r = frang_conn_limit(ra, conf); - if (r == TFW_BLOCK && conf->ip_block) { + r = frang_conn_limit(ra, dflt_vh->frang_gconf); + if (r == TFW_BLOCK && dflt_vh->frang_gconf->ip_block) { tfw_filter_block_ip(&cli->addr); tfw_client_put(cli); } spin_unlock(&ra->lock); + tfw_vhost_put(dflt_vh); return r; } @@ -414,6 +414,9 @@ frang_req_limit(FrangAcc *ra, unsigned int req_burst, unsigned int req_rate) unsigned int rsum = 0; int i = ts % FRANG_FREQ; + if (!req_burst && !req_rate) + return TFW_PASS; + if (ra->history[i].ts != ts) { ra->history[i].ts = ts; ra->history[i].conn_new = 0; @@ -464,16 +467,17 @@ frang_http_uri_len(const TfwHttpReq *req, FrangAcc *ra, unsigned int uri_len) */ static int __frang_http_field_len(const TfwHttpReq *req, FrangAcc *ra, - unsigned int field_len) + unsigned int field_len, unsigned int hdr_cnt) { const TfwStr *field, *end, *dup, *dup_end; - __FRANG_CFG_VAR(hdr_cnt, http_hdr_cnt); if (hdr_cnt && req->h_tbl->off >= hdr_cnt) { frang_limmsg("HTTP headers number", req->h_tbl->off, hdr_cnt, &FRANG_ACC2CLI(ra)->addr); return TFW_BLOCK; } + if (!field_len) + return TFW_PASS; FOR_EACH_HDR_FIELD(field, end, req) { TFW_STR_FOR_EACH_DUP(dup, field, dup_end) { @@ -490,18 +494,19 @@ __frang_http_field_len(const TfwHttpReq *req, FrangAcc *ra, } static int -frang_http_field_len(const TfwHttpReq *req, FrangAcc *ra, unsigned int field_len) +frang_http_field_len(const TfwHttpReq *req, FrangAcc *ra, unsigned int field_len, + unsigned int hdr_cnt) { TfwHttpParser *parser = &req->conn->parser; - if (parser->hdr.len > field_len) { + if (field_len && (parser->hdr.len > field_len)) { frang_limmsg("HTTP in-progress field length", parser->hdr.len, field_len, &FRANG_ACC2CLI(ra)->addr); return TFW_BLOCK; } - return __frang_http_field_len(req, ra, field_len); + return __frang_http_field_len(req, ra, field_len, hdr_cnt); } static int @@ -650,36 +655,6 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) return ret; } -/** - * Monotonically increasing time quantums. The configured @tframe - * is divided by FRANG_FREQ slots to get the quantums granularity. - */ -static unsigned int -frang_resp_quantum(unsigned short tframe) -{ - return jiffies * FRANG_FREQ / (tframe * HZ); -} - -static int -frang_bad_resp_limit(FrangAcc *ra, FrangHttpRespCodeBlock *resp_cblk) -{ - FrangRespCodeStat *stat = ra->resp_code_stat; - unsigned long cnt = 0; - const unsigned int ts = frang_resp_quantum(resp_cblk->tf); - int i = 0; - - for (; i < FRANG_FREQ; ++i) { - if (frang_time_in_frame(ts, stat[i].ts)) - cnt += stat[i].cnt; - } - if (cnt > resp_cblk->limit) { - frang_limmsg("http_resp_code_block limit", cnt, - resp_cblk->limit, &FRANG_ACC2CLI(ra)->addr); - return TFW_BLOCK; - } - return TFW_PASS; -} - /* * The GFSM states aren't hookable, so don't open the states definitions and * only start and finish states are present. @@ -694,25 +669,19 @@ enum { enum { Frang_Req_0 = 0, - Frang_Req_Hdr_Start, Frang_Req_Hdr_Method, Frang_Req_Hdr_UriLen, - Frang_Req_Hdr_FieldDup, - Frang_Req_Hdr_FieldLen, - Frang_Req_Hdr_FieldLenFinal, - Frang_Req_Hdr_Crlf, - Frang_Req_Hdr_Host, - Frang_Req_Hdr_ContentType, + Frang_Req_Hdr_Check, Frang_Req_Hdr_NoState, Frang_Req_Body_Start, - Frang_Req_Body_Timeout, - Frang_Req_Body_ChunkCnt, - Frang_Req_Body_Len, + Frang_Req_Body_End, Frang_Req_Body_NoState, + Frang_Req_Trailer, + Frang_Req_Done }; @@ -737,23 +706,16 @@ do { \ } while (0) static int -frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data) +frang_http_req_incomplete_hdrs_check(FrangAcc *ra, TfwFsmData *data, + FrangGlobCfg *fg_cfg) { - int r = TFW_PASS; TfwHttpReq *req = (TfwHttpReq *)data->req; struct sk_buff *skb = data->skb; struct sk_buff *head_skb = req->msg.skb_head; - __FRANG_CFG_VAR(hdr_tmt, clnt_hdr_timeout); - __FRANG_CFG_VAR(hchnk_cnt, http_hchunk_cnt); - T_FSM_INIT(Frang_Req_0, "frang"); + unsigned int hchnk_cnt = fg_cfg->http_hchunk_cnt; - BUG_ON(!ra); - BUG_ON(req != container_of(conn->msg, TfwHttpReq, msg)); - frang_dbg("check request for client %s, acc=%p\n", + frang_dbg("check incomplete request headers for client %s, acc=%p\n", &FRANG_ACC2CLI(ra)->addr, ra); - - spin_lock(&ra->lock); - /* * There's no need to check for header timeout if this is the very * first chunk of a request (first full separate SKB with data). @@ -761,155 +723,258 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data) * either block or move to one of header states. Then header timeout * is checked on each consecutive SKB with data - while we're still * in one of header processing states. - * - * 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 want - * to loop in FieldDup and FieldLen states. I guess that can be - * done with some clever FSM programming, but this is just simpler. */ - if (hdr_tmt && (skb != head_skb) && FSM_HDR_STATE(req->frang_st)) - { + if (fg_cfg->clnt_hdr_timeout && (skb != head_skb)) { unsigned long start = req->tm_header; - unsigned long delta = hdr_tmt; + unsigned long delta = fg_cfg->clnt_hdr_timeout; if (time_is_before_jiffies(start + delta)) { - frang_limmsg("client header timeout", jiffies - start, - delta, &FRANG_ACC2CLI(ra)->addr); - spin_unlock(&ra->lock); - return TFW_BLOCK; + frang_limmsg("client header timeout", + jiffies_to_msecs(jiffies - start), + jiffies_to_msecs(delta), + &FRANG_ACC2CLI(ra)->addr); + goto block; } } - /* Check for chunk count here to account for possible fragmentation - * in HTTP status line. The rationale for not making this one of FSM - * states is the same as for the code block above. + if (hchnk_cnt && (req->chunk_cnt > hchnk_cnt)) { + frang_limmsg("HTTP header chunk count", req->chunk_cnt, + hchnk_cnt, &FRANG_ACC2CLI(ra)->addr); + goto block; + } + + return TFW_PASS; +block: + return TFW_BLOCK; +} + +static int +frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, + FrangGlobCfg *fg_cfg, FrangCfg *f_cfg) +{ + TfwHttpReq *req = (TfwHttpReq *)data->req; + unsigned int body_len = f_cfg->http_body_len; + unsigned int bchunk_cnt = fg_cfg->http_bchunk_cnt; + unsigned long body_timeout = fg_cfg->clnt_body_timeout; + + struct sk_buff *skb = data->skb; + + frang_dbg("check incomplete request body for client %s, acc=%p\n", + &FRANG_ACC2CLI(ra)->addr, ra); + + /* CLRF after headers was parsed, but the body didn't arrive yet. */ + if (TFW_STR_EMPTY(&req->body)) + return TFW_PASS; + /* + * Ensure that HTTP request body is coming without delays. + * The timeout is between chunks of the body, so reset + * the start time after each check. */ - if (hchnk_cnt && FSM_HDR_STATE(req->frang_st)) { - if (req->chunk_cnt > hchnk_cnt) { - frang_limmsg("HTTP header chunk count", req->chunk_cnt, - hchnk_cnt, &FRANG_ACC2CLI(ra)->addr); - spin_unlock(&ra->lock); - return TFW_BLOCK; + if (body_timeout && req->tm_bchunk && (skb != req->body.skb)) { + unsigned long start = req->tm_bchunk; + unsigned long delta = body_timeout; + + if (time_is_before_jiffies(start + delta)) { + frang_limmsg("client body timeout", + jiffies_to_msecs(jiffies - start), + jiffies_to_msecs(delta), + &FRANG_ACC2CLI(ra)->addr); + goto block; } + req->tm_bchunk = jiffies; } - T_FSM_START(req->frang_st) { + /* Limit number of chunks in request body */ + if (bchunk_cnt && (req->chunk_cnt > bchunk_cnt)) { + frang_limmsg("HTTP body chunk count", req->chunk_cnt, + bchunk_cnt, &FRANG_ACC2CLI(ra)->addr); + goto block; + } + + if (body_len && (req->body.len > body_len)) { + frang_limmsg("HTTP body length", req->body.len, + body_len, &FRANG_ACC2CLI(ra)->addr); + goto block; + } + return TFW_PASS; +block: + return TFW_BLOCK; +} + +static int +frang_http_req_trailer_check(FrangAcc *ra, TfwFsmData *data, + FrangGlobCfg *fg_cfg, FrangCfg *f_cfg) +{ + int r = TFW_PASS; + TfwHttpReq *req = (TfwHttpReq *)data->req; + const TfwStr *field, *end, *dup, *dup_end; + + if (!test_bit(TFW_HTTP_B_CHUNKED_TRAILER, req->flags)) + return TFW_PASS; /* - * New HTTP request. Initial state. Check the limits that - * do not depend on contents of HTTP request. Note that - * connection-related limits are implemented as callbacks - * that run when a connection is established or destroyed. + * Don't use special settings for the trailer part, keep on + * using body limits. */ - T_FSM_STATE(Frang_Req_0) { - __FRANG_CFG_VAR(req_burst, req_burst); - __FRANG_CFG_VAR(req_rate, req_rate); - __FRANG_CFG_VAR(resp_cblk, http_resp_code_block); - if (req_burst || req_rate) - r = frang_req_limit(ra, req_burst, req_rate); - if (r == TFW_PASS && resp_cblk) - r = frang_bad_resp_limit(ra, resp_cblk); - __FRANG_FSM_MOVE(Frang_Req_Hdr_Start); + r = frang_http_req_incomplete_body_check(ra, data, fg_cfg, f_cfg); + if (test_bit(TFW_HTTP_B_FIELD_DUPENTRY, req->flags)) { + frang_msg("duplicate header field found", + &FRANG_ACC2CLI(ra)->addr, "\n"); + return TFW_BLOCK; } + if (!r) + r = frang_http_field_len(req, ra, f_cfg->http_field_len, + f_cfg->http_hdr_cnt); + if (r) + return r; + if (!tfw_http_parse_is_done((TfwHttpMsg *)req)) + return TFW_POSTPONE; /* - * Prepare for HTTP request header checks. Set the time - * the header started coming in. Set starting position - * for checking raw (non-special) headers. + * Block request if the same header appear in both main and + * trailer headers part. Some intermediates doesn't read trailers, so + * request processing may differ. */ - T_FSM_STATE(Frang_Req_Hdr_Start) { - if (hdr_tmt) { - req->tm_header = jiffies; + FOR_EACH_HDR_FIELD(field, end, req) { + int trailers = 0, dups = 0; + TFW_STR_FOR_EACH_DUP(dup, field, dup_end) { + trailers += !!(dup->flags & TFW_STR_TRAILER); + dups += 1; + } + if (trailers && (dups != trailers)) { + frang_msg("HTTP field appear in header and trailer " + "for client %p", + &FRANG_ACC2CLI(ra)->addr, "\n"); + return TFW_BLOCK; } - T_FSM_JMP(Frang_Req_Hdr_Method); } + return r; +} + +static int +frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, + TfwVhost *dvh) +{ + int r = TFW_PASS; + TfwHttpReq *req = (TfwHttpReq *)data->req; + FrangCfg *f_cfg = NULL; + FrangGlobCfg *fg_cfg = NULL; + T_FSM_INIT(Frang_Req_0, "frang"); + + BUG_ON(!ra); + BUG_ON(req != container_of(conn->msg, TfwHttpReq, msg)); + frang_dbg("check request for client %s, acc=%p\n", + &FRANG_ACC2CLI(ra)->addr, ra); + + if (req->vhost) { + /* Default vhost has no 'vhost_dflt' member set. */ + fg_cfg = req->vhost->vhost_dflt + ? req->vhost->vhost_dflt->frang_gconf + : req->vhost->frang_gconf; + f_cfg = req->location ? req->location->frang_cfg + : req->vhost->loc_dflt->frang_cfg; + } + else { + fg_cfg = dvh->frang_gconf; + f_cfg = dvh->loc_dflt->frang_cfg; + } + if (WARN_ON_ONCE(!fg_cfg || !f_cfg)) + return TFW_BLOCK; + + spin_lock(&ra->lock); + + /* + * Detect slowris attack first, and then proceed with more precise + * checks. This is not an FSM state, because the checks are required + * every time a new request chunk is received and will be present in + * every FSM state. + */ + if (req->frang_st < Frang_Req_Hdr_NoState) + r = frang_http_req_incomplete_hdrs_check(ra, data, fg_cfg); + else + r = frang_http_req_incomplete_body_check(ra, data, fg_cfg, + f_cfg); + if (r) { + spin_unlock(&ra->lock); + return r; + } + + T_FSM_START(req->frang_st) { + /* - * Ensure that HTTP request method is one of those - * defined by a user. + * New HTTP request. Initial state. Check the limits that + * do not depend on contents of HTTP request. Note that + * connection-related limits are implemented as callbacks + * that run when a connection is established or destroyed. */ + T_FSM_STATE(Frang_Req_0) { + r = frang_req_limit(ra, fg_cfg->req_burst, fg_cfg->req_rate); + /* Set the time the header started coming in. */ + req->tm_header = jiffies; + __FRANG_FSM_MOVE(Frang_Req_Hdr_Method); + } + + /* Ensure that HTTP request method is one of those defined by a user. */ T_FSM_STATE(Frang_Req_Hdr_Method) { - __FRANG_CFG_VAR(m_mask, http_methods_mask); - if (m_mask) { + if (f_cfg->http_methods_mask) { if (req->method == _TFW_HTTP_METH_NONE) { T_FSM_EXIT(); } - r = frang_http_methods(req, ra, m_mask); + r = frang_http_methods(req, ra, + f_cfg->http_methods_mask); } __FRANG_FSM_MOVE(Frang_Req_Hdr_UriLen); } /* Ensure that length of URI is within limits. */ T_FSM_STATE(Frang_Req_Hdr_UriLen) { - __FRANG_CFG_VAR(uri_len, http_uri_len); - if (uri_len) { - r = frang_http_uri_len(req, ra, uri_len); + if (f_cfg->http_uri_len) { + r = frang_http_uri_len(req, ra, f_cfg->http_uri_len); if (!(req->uri_path.flags & TFW_STR_COMPLETE)) __FRANG_FSM_JUMP_EXIT(Frang_Req_Hdr_UriLen); } - __FRANG_FSM_MOVE(Frang_Req_Hdr_FieldDup); + __FRANG_FSM_MOVE(Frang_Req_Hdr_Check); } - /* Ensure that singular header fields are not duplicated. */ - T_FSM_STATE(Frang_Req_Hdr_FieldDup) { + /* + * Headers are not fully parsed, a new request chunk was received. + * Test that all currently received headers doesn't overcome frang + * limits. + */ + T_FSM_STATE(Frang_Req_Hdr_Check) { if (test_bit(TFW_HTTP_B_FIELD_DUPENTRY, req->flags)) { frang_msg("duplicate header field found", &FRANG_ACC2CLI(ra)->addr, "\n"); r = TFW_BLOCK; + T_FSM_EXIT(); } - __FRANG_FSM_MOVE(Frang_Req_Hdr_FieldLen); - } - - /* Ensure that length of all parsed headers fields is within limits. */ - T_FSM_STATE(Frang_Req_Hdr_FieldLen) { - __FRANG_CFG_VAR(field_len, http_field_len); - if (field_len) - r = frang_http_field_len(req, ra, field_len); - __FRANG_FSM_MOVE(Frang_Req_Hdr_Crlf); - } - - /* - * See if the full HTTP header is processed. - * If not, continue checks on header fields. - */ - T_FSM_STATE(Frang_Req_Hdr_Crlf) { - if (req->crlf.flags & TFW_STR_COMPLETE) - T_FSM_JMP(Frang_Req_Hdr_FieldLenFinal); - __FRANG_FSM_JUMP_EXIT(Frang_Req_Hdr_FieldDup); - } - - /* - * Full HTTP header has been processed, and any possible - * header fields are collected. Run final checks on them. - */ - T_FSM_STATE(Frang_Req_Hdr_FieldLenFinal) { - __FRANG_CFG_VAR(field_len, http_field_len); - if (field_len) - r = __frang_http_field_len(req, ra, field_len); - __FRANG_FSM_MOVE(Frang_Req_Hdr_Host); - } + r = frang_http_field_len(req, ra, f_cfg->http_field_len, + f_cfg->http_hdr_cnt); + if (r) + T_FSM_EXIT(); + + /* Headers are not fully parsed yet. */ + if (!(req->crlf.flags & TFW_STR_COMPLETE)) + __FRANG_FSM_JUMP_EXIT(Frang_Req_Hdr_UriLen); + /* + * Full HTTP header has been processed, and any possible + * header fields are collected. Run final checks on them. + */ - /* Ensure presence and the value of Host: header field. */ - T_FSM_STATE(Frang_Req_Hdr_Host) { - __FRANG_CFG_VAR(host_required, http_host_required); - if (host_required) - r = frang_http_host_check(req, ra); - __FRANG_FSM_MOVE(Frang_Req_Hdr_ContentType); - } + /* Ensure presence and the value of Host: header field. */ + if (f_cfg->http_host_required + && (r = frang_http_host_check(req, ra))) + { + T_FSM_EXIT(); + } + /* + * Ensure presence of Content-Type: header field. + * Ensure that the value is one of those defined by a user. + */ + if (f_cfg->http_ct_required || f_cfg->http_ct_vals) + r = frang_http_ct_check(req, ra, f_cfg->http_ct_vals); - /* - * Ensure presence of Content-Type: header field. - * Ensure that the value is one of those defined by a user. - */ - T_FSM_STATE(Frang_Req_Hdr_ContentType) { - __FRANG_CFG_VAR(ct_required, http_ct_required); - __FRANG_CFG_VAR(ct_vals, http_ct_vals); - if (ct_required || ct_vals) - r = frang_http_ct_check(req, ra, ct_vals); __FRANG_FSM_MOVE(Frang_Req_Body_Start); } @@ -918,62 +983,29 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data) * Set the time the body started coming in. */ T_FSM_STATE(Frang_Req_Body_Start) { - __FRANG_CFG_VAR(body_len, http_body_len); - __FRANG_CFG_VAR(body_timeout, clnt_body_timeout); - __FRANG_CFG_VAR(bchunk_cnt, http_bchunk_cnt); - if (body_len || body_timeout || bchunk_cnt) { - req->chunk_cnt = 0; /* start counting body chunks now */ - req->tm_bchunk = jiffies; - __FRANG_FSM_MOVE(Frang_Req_Body_ChunkCnt); - } - __FRANG_FSM_JUMP_EXIT(Frang_Req_Done); + req->chunk_cnt = 0; /* start counting body chunks now. */ + req->tm_bchunk = jiffies; + r = frang_http_req_incomplete_body_check(ra, data, fg_cfg, + f_cfg); + __FRANG_FSM_MOVE(Frang_Req_Body_End); } /* - * Ensure that HTTP request body is coming without delays. - * The timeout is between chunks of the body, so reset - * the start time after each check. + * Body is not fully parsed, a new body chunk was received. */ - T_FSM_STATE(Frang_Req_Body_Timeout) { - /* - * Note that this state is skipped on the first data SKB - * with body part as obviously no timeout has occurred yet. - */ - __FRANG_CFG_VAR(body_timeout, clnt_body_timeout); - if (body_timeout) { - unsigned long start = req->tm_bchunk; - unsigned long delta = body_timeout; - - if (time_is_before_jiffies(start + delta)) { - frang_limmsg("client body timeout", - jiffies - start, delta, - &FRANG_ACC2CLI(ra)->addr); - r = TFW_BLOCK; - } - } - __FRANG_FSM_MOVE(Frang_Req_Body_ChunkCnt); - } + T_FSM_STATE(Frang_Req_Body_End) { - /* Limit number of chunks in request body */ - T_FSM_STATE(Frang_Req_Body_ChunkCnt) { - __FRANG_CFG_VAR(bchunk_cnt, http_bchunk_cnt); - if (bchunk_cnt && req->chunk_cnt > bchunk_cnt) { - frang_limmsg("HTTP body chunk count", req->chunk_cnt, - bchunk_cnt, &FRANG_ACC2CLI(ra)->addr); - r = TFW_BLOCK; - } - __FRANG_FSM_MOVE(Frang_Req_Body_Len); + /* Body is not fully parsed yet. */ + if (!(req->body.flags & TFW_STR_COMPLETE)) + __FRANG_FSM_JUMP_EXIT(Frang_Req_Body_End); + + __FRANG_FSM_MOVE(Frang_Req_Trailer); } - /* Ensure that the length of HTTP request body is within limits. */ - T_FSM_STATE(Frang_Req_Body_Len) { - __FRANG_CFG_VAR(body_len, http_body_len); - if (body_len && (req->body.len > body_len)) { - frang_limmsg("HTTP body length", req->body.len, - body_len, &FRANG_ACC2CLI(ra)->addr); - r = TFW_BLOCK; - } - __FRANG_FSM_JUMP_EXIT(Frang_Req_Body_Timeout); + /* Trailer headers. */ + T_FSM_STATE(Frang_Req_Trailer) { + r = frang_http_req_trailer_check(ra, data, fg_cfg, f_cfg); + __FRANG_FSM_MOVE(Frang_Req_Done); } /* All limits are verified for current request. */ @@ -996,7 +1028,7 @@ frang_http_req_handler(void *obj, TfwFsmData *data) int r; TfwConn *conn = (TfwConn *)obj; FrangAcc *ra = conn->sk->sk_security; - bool ip_block = tfw_vhost_global_frang_cfg()->ip_block; + TfwVhost *dvh = NULL; TfwHttpReq *req = (TfwHttpReq *)data->req; if (req->peer) @@ -1005,9 +1037,13 @@ frang_http_req_handler(void *obj, TfwFsmData *data) if (test_bit(TFW_HTTP_B_WHITELIST, ((TfwHttpReq *)data->req)->flags)) return TFW_PASS; - r = frang_http_req_process(ra, conn, data); - if (r == TFW_BLOCK && ip_block) + dvh = tfw_vhost_lookup_default(); + if (WARN_ON_ONCE(!dvh)) + return TFW_BLOCK; + r = frang_http_req_process(ra, conn, data, dvh); + if (r == TFW_BLOCK && dvh->frang_gconf->ip_block) tfw_filter_block_ip(&FRANG_ACC2CLI(ra)->addr); + tfw_vhost_put(dvh); return r; } @@ -1020,8 +1056,9 @@ static int frang_resp_process(TfwHttpResp *resp) { TfwHttpReq *req = resp->req; - __FRANG_CFG_VAR(body_len, http_body_len); TfwAddr *cli_addr = NULL; + TfwLocation *loc = req->location ? : req->vhost->loc_dflt; + unsigned long body_len = loc->frang_cfg->http_body_len; int r = TFW_PASS; if (!body_len) @@ -1051,20 +1088,61 @@ frang_resp_process(TfwHttpResp *resp) return r; } -/* - * Check response code and record it if it's listed in the filter. - * Called from tfw_http_resp_fwd() by tfw_gfsm_move() - * Always returns TFW_PASS because this handler is needed - * for collecting purposes only. +/** + * Monotonically increasing time quantums. The configured @tframe + * is divided by FRANG_FREQ slots to get the quantums granularity. + */ +static inline unsigned int +frang_resp_quantum(unsigned short tframe) +{ + return jiffies * FRANG_FREQ / (tframe * HZ); +} + +static int +frang_resp_code_limit(FrangAcc *ra, FrangHttpRespCodeBlock *resp_cblk) +{ + FrangRespCodeStat *stat = ra->resp_code_stat; + unsigned long cnt = 0; + const unsigned int ts = frang_resp_quantum(resp_cblk->tf); + int i = 0; + + i = ts % FRANG_FREQ; + if (ts != stat[i].ts) { + stat[i].ts = ts; + stat[i].cnt = 1; + } else { + ++stat[i].cnt; + } + for (i = 0; i < FRANG_FREQ; ++i) { + if (frang_time_in_frame(ts, stat[i].ts)) + cnt += stat[i].cnt; + } + if (cnt > resp_cblk->limit) { + frang_limmsg("http_resp_code_block limit", cnt, + resp_cblk->limit, &FRANG_ACC2CLI(ra)->addr); + return TFW_BLOCK; + } + + return TFW_PASS; +} + +/** + * Block client connection if not allowed response code appears too frequently + * in responses for that client. Called from tfw_http_resp_fwd() by + * tfw_gfsm_move(). + * Always returns TFW_PASS because there is no error in processing response, + * client may do something illegal and is to be blocked. Allow upper levels + * to continue working with the response and the server connection. */ static int frang_resp_fwd_process(TfwHttpResp *resp) { - unsigned int ts, i; + int r; FrangAcc *ra; - FrangRespCodeStat *stat; TfwHttpReq *req = resp->req; - __FRANG_CFG_VAR(conf, http_resp_code_block); + FrangCfg *fcfg = req->location ? req->location->frang_cfg + : req->vhost->loc_dflt->frang_cfg; + FrangHttpRespCodeBlock *conf = fcfg->http_resp_code_block; /* * Requests originated by Health Monitor are generated by Tempesta, @@ -1077,7 +1155,6 @@ frang_resp_fwd_process(TfwHttpResp *resp) ra = (FrangAcc *)req->conn->sk->sk_security; if (req->peer) ra = FRANG_CLI2ACC(req->peer); - stat = ra->resp_code_stat; frang_dbg("client %s check response %d, acc=%p\n", &FRANG_ACC2CLI(ra)->addr, resp->status, ra); @@ -1086,18 +1163,19 @@ frang_resp_fwd_process(TfwHttpResp *resp) return TFW_PASS; spin_lock(&ra->lock); + r = frang_resp_code_limit(ra, conf); + spin_unlock(&ra->lock); - ts = frang_resp_quantum(conf->tf); - i = ts % FRANG_FREQ; - if (ts != stat[i].ts) { - stat[i].ts = ts; - stat[i].cnt = 1; - } else { - ++stat[i].cnt; + if (r == TFW_BLOCK) { + /* Default vhost has no 'vhost_dflt' member set. */ + FrangGlobCfg *fg_cfg = req->vhost->vhost_dflt + ? req->vhost->vhost_dflt->frang_gconf + : req->vhost->frang_gconf; + tfw_cli_conn_close_all_sync((TfwClient *)req->conn->peer); + if (fg_cfg->ip_block) + tfw_filter_block_ip(&FRANG_ACC2CLI(ra)->addr); } - spin_unlock(&ra->lock); - return TFW_PASS; } diff --git a/tempesta_fw/http_parser.c b/tempesta_fw/http_parser.c index 8ff1a517e8..85f9c266a7 100644 --- a/tempesta_fw/http_parser.c +++ b/tempesta_fw/http_parser.c @@ -142,6 +142,8 @@ do { \ #define __FSM_FINISH(m) \ done: \ + if (r == TFW_PASS) \ + __set_bit(TFW_HTTP_B_FULLY_PARSED, msg->flags); \ /* Remaining number of bytes to process in the data chunk. */ \ *parsed = __data_off(p); @@ -699,6 +701,15 @@ __hbh_parser_add_data(TfwHttpMsg *hm, char *data, unsigned long len, bool last) return 0; } +static void +mark_trailer_hdr(TfwHttpMsg *hm, TfwStr *hdr) +{ + if (hm->crlf.flags & TFW_STR_COMPLETE) { + hdr->flags |= TFW_STR_TRAILER; + __set_bit(TFW_HTTP_B_CHUNKED_TRAILER, hm->flags); + } +} + /* * Helping state identifiers used to define which jump address an FSM should * set as the entry point. @@ -886,6 +897,7 @@ __FSM_STATE(st_curr) { \ /* The header value is fully parsed, move forward. */ \ if (saveval) \ __msg_hdr_chunk_fixup(p, __fsm_n); \ + mark_trailer_hdr(msg, &parser->hdr); \ parser->_i_st = &&RGen_EoL; \ parser->_hdr_tag = id; \ __FSM_MOVE_n(RGen_OWS, __fsm_n); /* skip OWS */ \ @@ -925,6 +937,7 @@ __FSM_STATE(st_curr) { \ if (saveval) \ __msg_hdr_chunk_fixup(p, __fsm_n); \ mark_raw_hbh(msg, &parser->hdr); \ + mark_trailer_hdr(msg, &parser->hdr); \ parser->_i_st = &&RGen_EoL; \ parser->_hdr_tag = TFW_HTTP_HDR_RAW; \ __FSM_MOVE_n(RGen_OWS, __fsm_n); /* skip OWS */ \ @@ -963,6 +976,7 @@ __FSM_STATE(RGen_HdrOtherV) { \ TFW_PARSER_BLOCK(RGen_HdrOtherV); \ __msg_hdr_chunk_fixup(data, __data_off(p + __fsm_sz)); \ mark_raw_hbh(msg, &parser->hdr); \ + mark_trailer_hdr(msg, &parser->hdr); \ __FSM_MOVE_n(RGen_EoL, __fsm_sz); \ } @@ -4907,3 +4921,9 @@ tfw_http_parse_resp(void *resp_data, unsigned char *data, size_t len, return r; } STACK_FRAME_NON_STANDARD(tfw_http_parse_resp); + +bool +tfw_http_parse_is_done(TfwHttpMsg *hm) +{ + return test_bit(TFW_HTTP_B_FULLY_PARSED, hm->flags); +} diff --git a/tempesta_fw/http_parser.h b/tempesta_fw/http_parser.h index 86e340f386..1d164116dd 100644 --- a/tempesta_fw/http_parser.h +++ b/tempesta_fw/http_parser.h @@ -108,5 +108,6 @@ int tfw_http_parse_req(void *req_data, unsigned char *data, size_t len, int tfw_http_parse_resp(void *resp_data, unsigned char *data, size_t len, unsigned int *parsed); int tfw_http_parse_terminate(TfwHttpMsg *hm); +bool tfw_http_parse_is_done(TfwHttpMsg *hm); #endif /* __TFW_HTTP_PARSER_H__ */ diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index eada228d4b..062d86ef4b 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -289,6 +289,12 @@ __cli_conn_close_cb(TfwConn *conn) return tfw_connection_close(conn, false); } +static int +__cli_conn_close_sync_cb(TfwConn *conn) +{ + return tfw_connection_close(conn, true); +} + static int tfw_cli_conn_close_all(void *data) { @@ -298,6 +304,14 @@ tfw_cli_conn_close_all(void *data) return tfw_peer_for_each_conn(cli, conn, list, __cli_conn_close_cb); } +int tfw_cli_conn_close_all_sync(TfwClient *cli) +{ + TfwConn *conn; + + return tfw_peer_for_each_conn(cli, conn, list, + __cli_conn_close_sync_cb); +} + /* * ------------------------------------------------------------------------ * Listening socket handling. diff --git a/tempesta_fw/str.h b/tempesta_fw/str.h index 424f2cb621..89682989ec 100644 --- a/tempesta_fw/str.h +++ b/tempesta_fw/str.h @@ -202,6 +202,8 @@ size_t tfw_ultohex(unsigned long ai, char *buf, unsigned int len); #define TFW_STR_HBH_HDR 0x10 /* Weak identifier was set for Etag value. */ #define TFW_STR_ETAG_WEAK 0x20 +/* Trailer header. */ +#define TFW_STR_TRAILER 0x40 /* * @ptr - pointer to string data or array of nested strings; diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index a9b64f9045..309c247297 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -2217,16 +2217,6 @@ tfw_vhost_start(void) { TfwVhostList *vh_list; - if (!tfw_runstate_is_reconfig()) { - /* Convert Frang global timeouts to jiffies for convenience */ - frang_cfg.clnt_hdr_timeout = - *(unsigned int *)&frang_cfg.clnt_hdr_timeout * - (unsigned long)HZ; - frang_cfg.clnt_body_timeout = - *(unsigned int *)&frang_cfg.clnt_body_timeout * - (unsigned long)HZ; - } - rcu_read_lock(); vh_list = rcu_dereference(tfw_vhosts); rcu_read_unlock(); diff --git a/tempesta_fw/vhost.h b/tempesta_fw/vhost.h index 9dd17ca9dc..92f022511d 100644 --- a/tempesta_fw/vhost.h +++ b/tempesta_fw/vhost.h @@ -219,7 +219,6 @@ bool tfw_vhost_is_default_reconfig(TfwVhost *vhost); TfwSrvConn *tfw_vhost_get_srv_conn(TfwMsg *msg); TfwVhost *tfw_vhost_new(const char *name); TfwGlobal *tfw_vhost_get_global(void); -TfwLocation *tfw_vhost_get_global_location(void); TfwHdrMods *tfw_vhost_get_hdr_mods(TfwLocation *loc, TfwVhost *vhost, int mod_type); From ce24da0fb33f1ed1ade2a4a873968255da7992ee Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Sun, 26 May 2019 04:26:48 +0500 Subject: [PATCH 04/20] Update configuration example --- etc/tempesta_fw.conf | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index 45c372972e..eced5b0a71 100644 --- a/etc/tempesta_fw.conf +++ b/etc/tempesta_fw.conf @@ -921,7 +921,10 @@ # TAG: frang_limits # -# The section containing static limits for the classifier. +# The section containing static limits for the classifier. Can apper at top +# level, inside 'vhost' directive, inside 'location' directive. Once the +# directive is listed in the configuration it redefine default vaues for +# following and nested sections of 'vhost' and 'location' directives. # # Syntax: # frang_limits { From 2665940cbaa70b346a60c4dda78879cc6c799063 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Thu, 25 Jul 2019 17:58:25 +0500 Subject: [PATCH 05/20] Update deprecated macroses # Conflicts: # tempesta_fw/vhost.c --- tempesta_fw/http_limits.c | 14 ++-- tempesta_fw/vhost.c | 168 +++++++++++++++++++------------------- 2 files changed, 91 insertions(+), 91 deletions(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 7b9eff2f92..2f2986f435 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -264,7 +264,7 @@ typedef struct { do { \ char abuf[TFW_ADDR_STR_BUF_SIZE] = {0}; \ tfw_addr_fmt(addr, TFW_NO_PORT, abuf); \ - TFW_DBG("frang: " fmt_msg, abuf, ##__VA_ARGS__); \ + T_DBG("frang: " fmt_msg, abuf, ##__VA_ARGS__); \ } while (0) #else #define frang_dbg(...) @@ -348,7 +348,7 @@ frang_conn_new(struct sock *sk) ss_getpeername(sk, &addr); cli = tfw_client_obtain(addr, NULL, NULL, __frang_init_acc); if (unlikely(!cli)) { - TFW_ERR("can't obtain a client for frang accounting\n"); + T_ERR("can't obtain a client for frang accounting\n"); tfw_vhost_put(dflt_vh); return TFW_BLOCK; } @@ -626,7 +626,7 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) hdrhost = tfw_pool_alloc(req->pool, field.len + 1); if (unlikely(!hdrhost)) { - TFW_ERR("Can not allocate memory\n"); + T_ERR("Can not allocate memory\n"); return TFW_BLOCK; } tfw_str_to_cstr(&field, hdrhost, field.len + 1); @@ -1292,7 +1292,7 @@ tfw_http_limits_hooks_register(void) h->hook_state, h->fsm_id, h->st0); if (h->prio < 0) { - TFW_ERR_NL("frang: can't register %s hook\n", h->name); + T_ERR_NL("frang: can't register %s hook\n", h->name); return -EINVAL; } } @@ -1313,13 +1313,13 @@ tfw_http_limits_init(void) r = tfw_gfsm_register_fsm(TFW_FSM_FRANG_REQ, frang_http_req_handler); if (r) { - TFW_ERR_NL("frang: can't register request fsm\n"); + T_ERR_NL("frang: can't register request fsm\n"); goto err_fsm; } r = tfw_gfsm_register_fsm(TFW_FSM_FRANG_RESP, frang_resp_handler); if (r) { - TFW_ERR_NL("frang: can't register response fsm\n"); + T_ERR_NL("frang: can't register response fsm\n"); goto err_fsm_resp; } @@ -1342,7 +1342,7 @@ tfw_http_limits_init(void) void tfw_http_limits_exit(void) { - TFW_DBG("frang exit\n"); + T_DBG("frang exit\n"); tfw_http_limits_hooks_remove(); tfw_gfsm_unregister_fsm(TFW_FSM_FRANG_RESP); diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index 309c247297..ddf0977235 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -297,20 +297,20 @@ tfw_vhost_get_srv_conn(TfwMsg *msg) if (unlikely(!main_sg)) return NULL; - TFW_DBG2("vhost: use server group: '%s'\n", main_sg->name); + T_DBG2("vhost: use server group: '%s'\n", main_sg->name); if (likely(main_sg->sched)) srv_conn = main_sg->sched->sched_sg_conn(msg, main_sg); if (unlikely(!srv_conn && backup_sg && backup_sg->sched)) { - TFW_DBG("vhost: the main group is offline, use backup: '%s'\n", - backup_sg->name); + T_DBG("vhost: the main group is offline, use backup: '%s'\n", + backup_sg->name); srv_conn = backup_sg->sched->sched_sg_conn(msg, backup_sg); } if (unlikely(!srv_conn)) - TFW_DBG2("vhost: Unable to select server from group '%s'\n", - backup_sg ? backup_sg->name : main_sg->name); + T_DBG2("vhost: Unable to select server from group '%s'\n", + backup_sg ? backup_sg->name : main_sg->name); return srv_conn; } @@ -587,12 +587,12 @@ tfw_cfgop_nonidempotent(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc) < _TFW_HTTP_METH_COUNT); if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", - cs->name); + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + cs->name); return -EINVAL; } if (ce->val_n != 3) { - TFW_ERR_NL("%s: Invalid number of arguments.\n", cs->name); + T_ERR_NL("%s: Invalid number of arguments.\n", cs->name); return -EINVAL; } @@ -600,8 +600,8 @@ tfw_cfgop_nonidempotent(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc) in_method = ce->vals[0]; ret = tfw_cfg_map_enum(tfw_method_enum, in_method, &method); if (ret) { - TFW_ERR_NL("Unsupported HTTP method: '%s %s'\n", - cs->name, in_method); + T_ERR_NL("Unsupported HTTP method: '%s %s'\n", + cs->name, in_method); return -EINVAL; } @@ -609,7 +609,7 @@ tfw_cfgop_nonidempotent(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc) in_op = ce->vals[1]; ret = tfw_cfg_map_enum(tfw_match_enum, in_op, &op); if (ret) { - TFW_ERR_NL("Unsupported match OP: '%s %s'\n", cs->name, in_op); + T_ERR_NL("Unsupported match OP: '%s %s'\n", cs->name, in_op); return -EINVAL; } @@ -623,10 +623,10 @@ tfw_cfgop_nonidempotent(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc) */ vh_dflt = tfw_vhosts_reconfig->vhost_dflt; if (tfw_nipdef_lookup_dup(loc, method, op, arg, len)) - TFW_WARN_NL("%s: Duplicate entry in location '%s': " - "'%s %s %s %s'\n", cs->name, - loc == vh_dflt->loc_dflt ? "default" : loc->arg, - cs->name, in_method, in_op, arg); + T_WARN_NL("%s: Duplicate entry in location '%s': " + "'%s %s %s %s'\n", cs->name, + loc == vh_dflt->loc_dflt ? "default" : loc->arg, + cs->name, in_method, in_op, arg); /* * Do not add a "duplicate" entry within a location. If the @@ -675,12 +675,12 @@ tfw_cfgop_mod_hdr_add(TfwLocation *loc, const char *name, const char *value, TfwHdrModsDesc *desc = &h_mods->hdrs[h_mods->sz]; if (h_mods->sz == TFW_USRHDRS_ARRAY_SZ) { - TFW_WARN_NL("Too lot of custom headers, %d supported.\n", - TFW_USRHDRS_ARRAY_SZ); + T_WARN_NL("Too lot of custom headers, %d supported.\n", + TFW_USRHDRS_ARRAY_SZ); return -EINVAL; } if (!(hdr = tfw_http_msg_make_hdr(loc->hdrs_pool, name, value))) { - TFW_WARN_NL("Can't create header.\n"); + T_WARN_NL("Can't create header.\n"); return -ENOMEM; } desc->hdr = hdr; @@ -707,8 +707,8 @@ tfw_cfgop_mod_hdr(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc, const char *value = NULL; if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", - cs->name); + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + cs->name); return -EINVAL; } switch (ce->val_n) @@ -720,7 +720,7 @@ tfw_cfgop_mod_hdr(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc, break; /* Fall through */ default: - TFW_ERR_NL("%s: Invalid number of values.\n", cs->name); + T_ERR_NL("%s: Invalid number of values.\n", cs->name); return -EINVAL; } @@ -884,13 +884,13 @@ tfw_cfgop_capolicy(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc, int cmd) BUG_ON((cmd != TFW_D_CACHE_BYPASS) && (cmd != TFW_D_CACHE_FULFILL)); if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", - cs->name); + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + cs->name); return -EINVAL; } if (ce->val_n < 2) { - TFW_ERR_NL("%s: Invalid number of arguments: %d\n", - cs->name, (int)ce->val_n); + T_ERR_NL("%s: Invalid number of arguments: %d\n", + cs->name, (int)ce->val_n); return -EINVAL; } @@ -899,7 +899,7 @@ tfw_cfgop_capolicy(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc, int cmd) /* Convert the match operator string to the enum value. */ ret = tfw_cfg_map_enum(tfw_match_enum, in_op, &op); if (ret) { - TFW_ERR_NL("Unknown match OP: '%s %s'\n", cs->name, in_op); + T_ERR_NL("Unknown match OP: '%s %s'\n", cs->name, in_op); return -EINVAL; } @@ -911,8 +911,8 @@ tfw_cfgop_capolicy(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc, int cmd) len = strlen(arg); if (tfw_capolicy_lookup(loc, cmd, op, arg, len)) { - TFW_WARN_NL("%s: Duplicate entry: '%s %s %s'\n", - cs->name, cs->name, in_op, arg); + T_WARN_NL("%s: Duplicate entry: '%s %s %s'\n", + cs->name, cs->name, in_op, arg); continue; } if (loc->capo_sz == TFW_CAPOLICY_ARRAY_SZ) @@ -1116,12 +1116,12 @@ tfw_cfgop_location_begin(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwVhost *vhost) const char *in_op, *arg; if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", cs->name); return -EINVAL; } if (ce->val_n != 2) { - TFW_ERR_NL("%s: Invalid number of arguments: %d\n", + T_ERR_NL("%s: Invalid number of arguments: %d\n", cs->name, (int)ce->val_n); return -EINVAL; } @@ -1134,21 +1134,21 @@ tfw_cfgop_location_begin(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwVhost *vhost) /* Convert the match operator string to the enum value. */ ret = tfw_cfg_map_enum(tfw_match_enum, in_op, &op); if (ret) { - TFW_ERR_NL("%s: Unknown match OP: '%s %s %s'\n", + T_ERR_NL("%s: Unknown match OP: '%s %s %s'\n", cs->name, cs->name, in_op, arg); return -EINVAL; } /* Make sure the location is not a duplicate. */ if (tfw_location_lookup(vhost, op, arg, len)) { - TFW_ERR_NL("%s: Duplicate entry: '%s %s %s'\n", + T_ERR_NL("%s: Duplicate entry: '%s %s %s'\n", cs->name, cs->name, in_op, arg); return -EINVAL; } if (vhost->loc_sz == TFW_LOCATION_ARRAY_SZ) { - TFW_ERR_NL("%s: There is no empty slots in '%s' vhost to" + T_ERR_NL("%s: There is no empty slots in '%s' vhost to" " add new location: '%s %s %s'\n", cs->name, vhost->name.data, cs->name, in_op, arg); return -EINVAL; @@ -1157,7 +1157,7 @@ tfw_cfgop_location_begin(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwVhost *vhost) /* Add new location and set it to be the current one. */ tfwcfg_this_location = tfw_location_new(vhost, op, arg, len); if (!tfwcfg_this_location) { - TFW_ERR_NL("%s: Unable to create new location: '%s %s %s'\n", + T_ERR_NL("%s: Unable to create new location: '%s %s %s'\n", cs->name, cs->name, in_op, arg); return -ENOMEM; } @@ -1193,7 +1193,7 @@ tfw_cfgop_in_location_finish(TfwCfgSpec *cs) if (!tfw_vhost_is_default_reconfig(tfw_vhost_entry) && !tfwcfg_this_location->main_sg) { - TFW_ERR_NL("Directive 'proxy_pass' is not specified for" + T_ERR_NL("Directive 'proxy_pass' is not specified for" " location (with arg '%s') inside not default" " vhost '%s'.\n", tfwcfg_this_location->arg, tfw_vhost_entry->name.data); @@ -1295,7 +1295,7 @@ tfw_cfgop_cache_purge_acl(TfwCfgSpec *cs, TfwCfgEntry *ce) const char *val; if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", cs->name); return -EINVAL; } @@ -1304,20 +1304,20 @@ tfw_cfgop_cache_purge_acl(TfwCfgSpec *cs, TfwCfgEntry *ce) TfwAddr addr = { 0 }; if (tfw_addr_pton_cidr(val, &addr)) { - TFW_ERR_NL("%s: Invalid ACL entry: '%s'\n", - cs->name, val); + T_ERR_NL("%s: Invalid ACL entry: '%s'\n", + cs->name, val); return -EINVAL; } /* Make sure the address is not a duplicate. */ if (tfw_capuacl_lookup(&addr)) { - TFW_ERR_NL("%s: Duplicate IP address or prefix: '%s'\n", - cs->name, val); + T_ERR_NL("%s: Duplicate IP address or prefix: '%s'\n", + cs->name, val); return -EINVAL; } /* Add new ACL entry. */ if (tfw_global.capuacl_sz == TFW_CAPUACL_ARRAY_SZ) { - TFW_ERR_NL("%s: Unable to add new ACL: '%s'\n", - cs->name, val); + T_ERR_NL("%s: Unable to add new ACL: '%s'\n", + cs->name, val); return -EINVAL; } tfw_global.capuacl[tfw_global.capuacl_sz++] = addr; @@ -1337,8 +1337,8 @@ tfw_cfgop_cache_purge(TfwCfgSpec *cs, TfwCfgEntry *ce) const char *val; if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", - cs->name); + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + cs->name); return -EINVAL; } if (!ce->val_n) { @@ -1350,8 +1350,8 @@ tfw_cfgop_cache_purge(TfwCfgSpec *cs, TfwCfgEntry *ce) if (!strcasecmp(val, "invalidate")) { tfw_global.cache_purge_mode = TFW_D_CACHE_PURGE_INVALIDATE; } else { - TFW_ERR_NL("%s: unsupported argument: '%s'\n", - cs->name, val); + T_ERR_NL("%s: unsupported argument: '%s'\n", + cs->name, val); return -EINVAL; } } @@ -1371,13 +1371,13 @@ tfw_cfgop_hdr_via(TfwCfgSpec *cs, TfwCfgEntry *ce) size_t len; if (ce->attr_n) { - TFW_ERR_NL("%s: Arguments may not have the \'=\' sign\n", - cs->name); + T_ERR_NL("%s: Arguments may not have the \'=\' sign\n", + cs->name); return -EINVAL; } if (ce->val_n != 1) { - TFW_ERR_NL("%s: Invalid number of arguments: %d\n", - cs->name, (int)ce->val_n); + T_ERR_NL("%s: Invalid number of arguments: %d\n", + cs->name, (int)ce->val_n); return -EINVAL; } @@ -1401,9 +1401,9 @@ tfw_cfgop_vhost_check_flags(TfwSrvGroup *main_sg, TfwSrvGroup *backup_sg) int r = ((main_sg->flags & TFW_SRV_STICKY_FLAGS) ^ (backup_sg->flags & TFW_SRV_STICKY_FLAGS)); if (r) - TFW_ERR_NL("vhost: srv_groups '%s' and '%s' must " - "have the same sticky sessions settings\n", - main_sg->name, backup_sg->name); + T_ERR_NL("vhost: srv_groups '%s' and '%s' must " + "have the same sticky sessions settings\n", + main_sg->name, backup_sg->name); return r; } @@ -1417,16 +1417,16 @@ __tfw_cfgop_proxy_pass(const char *main_sg_nm, const char *backup_sg_nm, main_sg = tfw_sg_lookup_reconfig(main_sg_nm, strlen(main_sg_nm)); if (!main_sg) { - TFW_ERR_NL("proxy_pass: srv_group is not found: '%s'\n", - main_sg_nm); + T_ERR_NL("proxy_pass: srv_group is not found: '%s'\n", + main_sg_nm); return -EINVAL; } if (backup_sg_nm) { backup_sg = tfw_sg_lookup_reconfig(backup_sg_nm, strlen(backup_sg_nm)); if (!backup_sg) { - TFW_ERR_NL("proxy_pass: backup srv_group is not found:" - " '%s'\n", backup_sg_nm); + T_ERR_NL("proxy_pass: backup srv_group is not found:" + " '%s'\n", backup_sg_nm); r = -EINVAL; goto err; } @@ -1470,10 +1470,10 @@ tfw_cfgop_proxy_pass(TfwCfgSpec *cs, TfwCfgEntry *ce, TfwLocation *loc) && (!in_backup_sg || !strcasecmp(in_backup_sg, TFW_VH_DFT_NAME))) return 0; - TFW_ERR_NL("Default vhost must point to default server" - " group only, so it is not allowed to specify" - " any not default 'proxy_pass' directive inside of" - " default vhost.\n"); + T_ERR_NL("Default vhost must point to default server" + " group only, so it is not allowed to specify" + " any not default 'proxy_pass' directive inside of" + " default vhost.\n"); return -EINVAL; } return __tfw_cfgop_proxy_pass(in_main_sg, in_backup_sg, loc); @@ -1525,7 +1525,7 @@ tfw_vhost_create(const char *name) if (!(vhost = kzalloc(size, GFP_KERNEL))) { tfw_pool_destroy(pool); - TFW_ERR_NL("Cannot allocate vhost entry '%s'\n", name); + T_ERR_NL("Cannot allocate vhost entry '%s'\n", name); return NULL; } INIT_HLIST_NODE(&vhost->hlist); @@ -1621,16 +1621,16 @@ __tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce, int r = tfw_cfg_map_enum(frang_http_methods_enum, method_str, &method_id); if (r) { - TFW_ERR_NL("frang: invalid method: '%s'\n", method_str); + T_ERR_NL("frang: invalid method: '%s'\n", method_str); return -EINVAL; } - TFW_DBG3("frang: parsed method: %s => %d\n", - method_str, method_id); + T_DBG3("frang: parsed method: %s => %d\n", + method_str, method_id); methods_mask |= (1UL << method_id); } - TFW_DBG3("parsed methods_mask: %#lx\n", methods_mask); + T_DBG3("parsed methods_mask: %#lx\n", methods_mask); *cfg_methods_mask = methods_mask; return 0; } @@ -1680,7 +1680,7 @@ __tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) vals_pos->str = strs_pos; vals_pos->len = (len - 1); - TFW_DBG3("parsed Content-Type value: '%s'\n", in_str); + T_DBG3("parsed Content-Type value: '%s'\n", in_str); vals_pos++; strs_pos += len; @@ -1698,8 +1698,8 @@ frang_parse_ushort(const char *s, unsigned short *out) { int n; if (tfw_cfg_parse_int(s, &n)) { - TFW_ERR_NL("frang: http_resp_code_block: " - "\"%s\" isn't a valid value\n", s); + T_ERR_NL("frang: http_resp_code_block: " + "\"%s\" isn't a valid value\n", s); return -EINVAL; } if (tfw_cfg_check_range(n, 1, USHRT_MAX)) @@ -1720,13 +1720,13 @@ __tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, int n, i; if (ce->attr_n) { - TFW_ERR_NL("%s arguments may not have the \'=\' sign\n", - error_msg_begin); + T_ERR_NL("%s arguments may not have the \'=\' sign\n", + error_msg_begin); return -EINVAL; } if (ce->val_n < 3) { - TFW_ERR_NL("%s too few arguments\n", error_msg_begin); + T_ERR_NL("%s too few arguments\n", error_msg_begin); return -EINVAL; } @@ -1739,8 +1739,8 @@ __tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, while (--i >= 0) { if (tfw_cfg_parse_int(ce->vals[i], &n) || !tfw_http_resp_code_range(n)) { - TFW_ERR_NL("%s invalid HTTP code \"%s\"", - error_msg_begin, ce->vals[i]); + T_ERR_NL("%s invalid HTTP code \"%s\"", + error_msg_begin, ce->vals[i]); return -EINVAL; } /* Atomic restriction isn't needed here */ @@ -2073,14 +2073,14 @@ tfw_vhost_cfgstart(void) BUG_ON(tfw_vhosts_reconfig); tfw_vhosts_reconfig = kmalloc(sizeof(TfwVhostList), GFP_KERNEL); if (!tfw_vhosts_reconfig) { - TFW_ERR_NL("Unable to allocate vhosts' list.\n"); + T_ERR_NL("Unable to allocate vhosts' list.\n"); return -ENOMEM; } tfw_vhosts_reconfig->expl_dflt = false; hash_init(tfw_vhosts_reconfig->vh_hash); if(!(vh_dflt = tfw_vhost_new(TFW_VH_DFT_NAME))) { - TFW_ERR_NL("Unable to create default vhost.\n"); + T_ERR_NL("Unable to create default vhost.\n"); return -ENOMEM; } @@ -2139,13 +2139,13 @@ tfw_cfgop_vhost_begin(TfwCfgSpec *cs, TfwCfgEntry *ce) if (tfw_cfg_check_val_n(ce, 1)) return -EINVAL; if (ce->attr_n) { - TFW_ERR_NL("Unexpected attributes\n"); + T_ERR_NL("Unexpected attributes\n"); return -EINVAL; } hash_for_each(tfw_vhosts_reconfig->vh_hash, i, vhost, hlist) { if (!strcasecmp(vhost->name.data, ce->vals[0])) { - TFW_ERR_NL("Duplicate vhost entry: '%s'\n", - ce->vals[0]); + T_ERR_NL("Duplicate vhost entry: '%s'\n", + ce->vals[0]); return -EINVAL; } } @@ -2160,8 +2160,8 @@ tfw_cfgop_vhost_begin(TfwCfgSpec *cs, TfwCfgEntry *ce) return -ENOMEM; } else { if (!(tfw_vhost_entry = tfw_vhost_new(ce->vals[0]))) { - TFW_ERR_NL("Unable to create new vhost entry: '%s'\n", - ce->vals[0]); + T_ERR_NL("Unable to create new vhost entry: '%s'\n", + ce->vals[0]); return -ENOMEM; } } @@ -2180,9 +2180,9 @@ tfw_cfgop_vhost_finish(TfwCfgSpec *cs) BUG_ON(!tfw_vhost_entry); if (!tfw_vhost_entry->loc_dflt->main_sg) { BUG_ON(tfw_vhost_is_default_reconfig(tfw_vhost_entry)); - TFW_ERR_NL("Directive 'proxy_pass' is not specified" - " for not default vhost '%s'.\n", - tfw_vhost_entry->name.data); + T_ERR_NL("Directive 'proxy_pass' is not specified" + " for not default vhost '%s'.\n", + tfw_vhost_entry->name.data); return -EINVAL; } if ((r = tfw_tls_cert_cfg_finish(tfw_vhost_entry))) From ce1dd2fefa23979d587df7d8bf2286a80c84bd3f Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Fri, 1 Mar 2019 12:00:40 +0500 Subject: [PATCH 06/20] sample conf: add missing frang limits names --- etc/tempesta_fw.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index eced5b0a71..7b545be0b9 100644 --- a/etc/tempesta_fw.conf +++ b/etc/tempesta_fw.conf @@ -928,6 +928,7 @@ # # Syntax: # frang_limits { +# ip_block off|on; # request_rate NUM; # request_burst NUM; # connection_rate NUM; @@ -938,6 +939,9 @@ # http_uri_len NUM; # http_field_len NUM; # http_body_len NUM; +# http_header_cnt NUM; +# http_header_chunk_cnt NUM; +# http_body_chunk_cnt NUM; # http_host_required true|false; # http_methods [METHOD]...; # http_ct_required true|false; From 40023b83bfbfe6a47c233d58a517cf06fdd822de Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 25 Jun 2019 13:01:06 +0500 Subject: [PATCH 07/20] Fix rebase glitches --- tempesta_fw/vhost.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index ddf0977235..0ce508390d 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -1498,8 +1498,6 @@ tfw_vhost_destroy(TfwVhost *vhost) { int i; - T_DBG2("destroy vhost '%s'\n", vhost->name); - for (i = 0; i < vhost->loc_sz; ++i) tfw_location_del(&vhost->loc[i]); tfw_location_del(vhost->loc_dflt); From 7bdaa1217613e53132b5e502d72dc99669c48bb5 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 25 Jun 2019 15:12:18 +0500 Subject: [PATCH 08/20] Add more documentation for code as requested during code review. --- tempesta_fw/http.c | 22 ++++++++++++++++++++++ tempesta_fw/http_limits.c | 31 ++++++++++++++++++++++++++----- tempesta_fw/sock_clnt.c | 13 +++++++++++++ 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index 898e7b81a4..38d2d37ff8 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -3213,6 +3213,28 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data) * * In the same time location may differ between requests, so the * sticky module can't fill it. + * + * TODO: + * There are multiple ways to get target vhost: + * - search in HTTP chains (defined in the configuration by admin), + * very slow on big configurations; + * - get vhost from the HTTP session information (by Sticky cookie); + * - get Vhost according to TLS SNI header parsing. + * But vhost returned from all that functions can be very different + * depending on HTTP chain configuration due to it flexibility and + * client (attacker) behaviour. + * The most secure and a slow way: compare all of them and reject if + * at least some doesn't match. Can be too strict, service quality can + * be degraded. + * The fastest way: compute as minimum as possible: use TLS first + * (anyway we use it to send right certificate), then cookie, then HTTP + * chain. There can be possibility that a request will be forwarded to + * a wrong server. May happen when a single certificate is used to + * speak with multiple vhosts (multy-domain (SAN) certificate as + * globally default TLS certificate in Tempesta config file). + * The most reliable way: use the highest OSI level vhost: by HTTP + * session, if no -> by http chain, if no -> by TLS conn. + * */ if (!req->vhost) { req->vhost = tfw_http_tbl_vhost((TfwMsg *)req, &block); diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 2f2986f435..0aabe487df 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -338,11 +338,11 @@ frang_conn_new(struct sock *sk) TfwVhost *dflt_vh = tfw_vhost_lookup_default(); /* - * On reboot under heavy load global vhost may be not ready yet. - * Don't pass such connections, force client to reconnect later, when - * we will be ready. + * Default vhost configuration stores global frang settings, it's always + * available even on reload under heavy load. But the pointer comes + * from other module, take care of probable null-dereferences. */ - if (unlikely(!dflt_vh)) + if (WARN_ON_ONCE(!dflt_vh)) return TFW_BLOCK; ss_getpeername(sk, &addr); @@ -368,7 +368,20 @@ frang_conn_new(struct sock *sk) * TfwConn{}. */ sk->sk_security = ra; - + /* + * TBD: Since we can determine vhost by SNI field in TLS headers, there + * will be a desire to make all frang limits per-vhost. After some + * thinking I have found this counter-intuitive: The way, how the + * frang limits work will depend on too many conditions. E.g. + * TLS-enabled clients will use higher limits than non-TLS clients; + * the same client can break security rules for one vhost, but be a + * legitimate client for other vhosts, so it's a big question how to + * block him by IP or by connection resets; if multy-domain certificate + * (SAN) is configured, TLS clients will behave as non-TLS. Too + * complicated for administrator to understand how client is blocked + * and to configure it, while making some of the limits to be global + * for a single client is absolutely straight-forward. + */ r = frang_conn_limit(ra, dflt_vh->frang_gconf); if (r == TFW_BLOCK && dflt_vh->frang_gconf->ip_block) { tfw_filter_block_ip(&cli->addr); @@ -1163,6 +1176,14 @@ frang_resp_fwd_process(TfwHttpResp *resp) return TFW_PASS; spin_lock(&ra->lock); + /* + * According to backend response code attacker may be trying to crack + * the password. This security event must be triggered when the response + * received, and can't be triggered on request context because client + * can send a huge number of pipelined requests to crack the password + * and wait for the results. If the attack is spotted, block the client + * and wipe all it's received but not responded requests ASAP. + */ r = frang_resp_code_limit(ra, conf); spin_unlock(&ra->lock); diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 062d86ef4b..2851daf6bb 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -295,6 +295,12 @@ __cli_conn_close_sync_cb(TfwConn *conn) return tfw_connection_close(conn, true); } +/** + * Asynchronously close all client connections. Some connection close requests + * may be lost due to workqueue overrun. So the function must be closed + * repeatedly to guarantee that all the connection was closed. + * + */ static int tfw_cli_conn_close_all(void *data) { @@ -304,6 +310,13 @@ tfw_cli_conn_close_all(void *data) return tfw_peer_for_each_conn(cli, conn, list, __cli_conn_close_cb); } +/** + * Close all connections with given client, called on security events. Unlike + * asynchronous function above, this one must guarantee that all the close + * requests will be done. Attackers can spam Tempesta with lot of requests and + * connections, trying to cause work queue overrun and delay of security events + * handlers. To detach attackers efficiently, we have to use synchronous close. + */ int tfw_cli_conn_close_all_sync(TfwClient *cli) { TfwConn *conn; From a199430e64a79fda8b27fa392d2f069de04bea61 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 25 Jun 2019 16:20:11 +0500 Subject: [PATCH 09/20] frang: Reduce calculations in hot path --- tempesta_fw/http_limits.c | 7 +++---- tempesta_fw/http_limits.h | 3 +++ tempesta_fw/vhost.c | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 0aabe487df..5db301e1f8 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -197,9 +197,6 @@ static TempestaOps tempesta_ops = { * ------------------------------------------------------------------------ */ -/* We account users with FRANG_FREQ frequency per second. */ -#define FRANG_FREQ 8 - typedef struct { unsigned long ts; unsigned int conn_new; @@ -1104,11 +1101,13 @@ frang_resp_process(TfwHttpResp *resp) /** * Monotonically increasing time quantums. The configured @tframe * is divided by FRANG_FREQ slots to get the quantums granularity. + * To reduce calculations, tframe is already stored as result of multiplication + * operation, see __tfw_cfgop_frang_rsp_code_block(). */ static inline unsigned int frang_resp_quantum(unsigned short tframe) { - return jiffies * FRANG_FREQ / (tframe * HZ); + return jiffies / tframe; } static int diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index 5a39bf8cb2..70abd9c353 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -108,6 +108,9 @@ extern void tfw_classifier_unregister(void); * ------------------------------------------------------------------------ */ +/* We account users with FRANG_FREQ frequency per second. */ +#define FRANG_FREQ 8 + /** * Response code block setting * diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index 0ce508390d..ff49c67e7f 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -1755,6 +1755,8 @@ __tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, * closed */ tfw_client_set_expires_time(cb->tf); + /* Update time frame value to reduce calculations in hot-path. */ + cb->tf = (cb->tf * HZ) / FRANG_FREQ; return 0; } From f457bdd1c24dbbfcdc4c13eee1b82a486ad0fbcc Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 25 Jun 2019 17:53:48 +0500 Subject: [PATCH 10/20] Adress code review commeents --- tempesta_fw/http_limits.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 5db301e1f8..ca687f3d14 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -703,10 +703,6 @@ enum { TFW_FRANG_RESP_FSM_DONE = TFW_GFSM_FRANG_RESP_STATE(TFW_GFSM_STATE_LAST) }; -/* Extend basic FSM with necessary ad-hoc logic. */ -#define FSM_HDR_STATE(state) \ - ((state > Frang_Req_Hdr_Start) && (state < Frang_Req_Hdr_NoState)) - #define __FRANG_FSM_MOVE(st) T_FSM_MOVE(st, if (r) T_FSM_EXIT(); ) #define __FRANG_FSM_JUMP_EXIT(st) \ @@ -766,7 +762,6 @@ frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, unsigned int body_len = f_cfg->http_body_len; unsigned int bchunk_cnt = fg_cfg->http_bchunk_cnt; unsigned long body_timeout = fg_cfg->clnt_body_timeout; - struct sk_buff *skb = data->skb; frang_dbg("check incomplete request body for client %s, acc=%p\n", From ff103692d8e818ff58232f0f8a6829afbcf7d278 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 25 Jun 2019 18:21:37 +0500 Subject: [PATCH 11/20] frang: switchable request trailers checks --- tempesta_fw/http_limits.c | 37 ++++++++++++++++++++++++++++++++++++- tempesta_fw/http_limits.h | 1 + tempesta_fw/vhost.c | 26 ++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index ca687f3d14..3761bffc19 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -807,6 +807,38 @@ frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, return TFW_BLOCK; } +/* + * RFC 7230 Section-4.1.2: + * When a chunked message containing a non-empty trailer is received, + * the recipient MAY process the fields. + * + * RFC 7230 Section-4.1.2: + * ...a server SHOULD NOT + * generate trailer fields that it believes are necessary for the user + * agent to receive. Without a TE containing "trailers", the server + * ought to assume that the trailer fields might be silently discarded + * along the path to the user agent. This requirement allows + * intermediaries to forward a de-chunked message to an HTTP/1.0 + * recipient without buffering the entire response. + * + * RFC doesn't prohibit trailers in request, but it always speaks about + * trailers in response context. But request with trailer headers + * are valid http messages. Support for them is not documented, + * and implementation-dependent. E.g. Apache doesn't care about trailer + * headers, but ModSecurity for Apache does. + * https://swende.se/blog/HTTPChunked.html + * Some discussions also highlights that trailer headers are poorly + * supported on both servers and clients, while CDNs tend to add + * trailers. https://github.com/whatwg/fetch/issues/34 + * + * Since RFC doesn't speak clearly about trailer headers in requests, the + * following assumptions was used: + * - Our intermediaries on client side doesn't care about trailers and send + * them in the manner as the body. Thus frang's body limitations are used, + * not headers ones. + * - Same header may have different values depending on how the servers works + * with the trailer. Administrator can block that behaviour. + */ static int frang_http_req_trailer_check(FrangAcc *ra, TfwFsmData *data, FrangGlobCfg *fg_cfg, FrangCfg *f_cfg) @@ -838,8 +870,11 @@ frang_http_req_trailer_check(FrangAcc *ra, TfwFsmData *data, /* * Block request if the same header appear in both main and * trailer headers part. Some intermediates doesn't read trailers, so - * request processing may differ. + * request processing may depend on implementation. */ + if (!f_cfg->http_trailer_split) + return TFW_PASS; + FOR_EACH_HDR_FIELD(field, end, req) { int trailers = 0, dups = 0; TFW_STR_FOR_EACH_DUP(dup, field, dup_end) { diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index 70abd9c353..59108c2614 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -195,6 +195,7 @@ struct frang_vhost_cfg_t { bool http_ct_required; bool http_host_required; + bool http_trailer_split; }; #endif /* __HTTP_LIMITS__ */ diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index ff49c67e7f..c45ef1922b 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -1921,6 +1921,20 @@ tfw_cfgop_frang_ct_required(TfwCfgSpec *cs, TfwCfgEntry *ce) return r; } +static int +tfw_cfgop_frang_trailer_split(TfwCfgSpec *cs, TfwCfgEntry *ce) +{ + int r; + FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + + if (ce->dflt_value && cfg->http_trailer_split) + return 0; + cs->dest = &cfg->http_trailer_split; + r = tfw_cfg_set_bool(cs, ce); + cs->dest = NULL; + return r; +} + static int tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce) { @@ -2409,6 +2423,12 @@ static TfwCfgSpec tfw_global_frang_specs[] = { .handler = tfw_cfgop_frang_ct_required, .allow_reconfig = true, }, + { + .name = "http_trailer_split_allowed", + .deflt = "false", + .handler = tfw_cfgop_frang_trailer_split, + .allow_reconfig = true, + }, { .name = "http_methods", .deflt = "", @@ -2531,6 +2551,12 @@ static TfwCfgSpec tfw_vhost_frang_specs[] = { .handler = tfw_cfgop_frang_ct_required, .allow_reconfig = true, }, + { + .name = "http_trailer_split_allowed", + .deflt = "false", + .handler = tfw_cfgop_frang_trailer_split, + .allow_reconfig = true, + }, { .name = "http_methods", .deflt = "", From ba5c6f29d8d352531f75a71d2effddcc0ab56502 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Tue, 25 Jun 2019 18:34:42 +0500 Subject: [PATCH 12/20] bump copyrighs --- tempesta_fw/http_limits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index 59108c2614..22590a7b03 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2018 Tempesta Technologies, Inc. + * Copyright (C) 2015-2019 Tempesta Technologies, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by From e8bb321fa24ce8d371d72ff9680b2d1a421a54c2 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Sun, 14 Jul 2019 20:04:21 +0500 Subject: [PATCH 13/20] frang: add debug message, when the pessage is fully checked --- tempesta_fw/http_limits.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 3761bffc19..a4c8bce50a 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -1050,6 +1050,8 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, /* All limits are verified for current request. */ T_FSM_STATE(Frang_Req_Done) { + frang_dbg("checks done for client %s\n", + &FRANG_ACC2CLI(ra)->addr); tfw_gfsm_move(&conn->state, TFW_FRANG_REQ_FSM_DONE, data); T_FSM_EXIT(); } From 4666ff408ef500be1183cca3f449832871979baf Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Sun, 14 Jul 2019 20:37:14 +0500 Subject: [PATCH 14/20] frang: fix incorrect start state in frang callback --- tempesta_fw/http_limits.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index a4c8bce50a..f3fd9d71f3 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -1312,7 +1312,7 @@ static FrangGfsmHook frang_gfsm_hooks[] = { .prio = -1, .hook_state = TFW_HTTP_FSM_RESP_MSG_FWD, .fsm_id = TFW_FSM_FRANG_RESP, - .st0 = TFW_FRANG_RESP_FSM_INIT, + .st0 = TFW_FRANG_RESP_FSM_FWD, .name = "response_fwd", }, }; From 2735e514658f3b913f8670231defdba4df258fca Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Thu, 25 Jul 2019 19:44:59 +0500 Subject: [PATCH 15/20] fix typos --- etc/tempesta_fw.conf | 4 ++-- tempesta_fw/http.c | 8 ++++---- tempesta_fw/http_limits.c | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index 7b545be0b9..63e0479788 100644 --- a/etc/tempesta_fw.conf +++ b/etc/tempesta_fw.conf @@ -921,9 +921,9 @@ # TAG: frang_limits # -# The section containing static limits for the classifier. Can apper at top +# The section containing static limits for the classifier. Can appear at top # level, inside 'vhost' directive, inside 'location' directive. Once the -# directive is listed in the configuration it redefine default vaues for +# directive is listed in the configuration it redefines default values for the # following and nested sections of 'vhost' and 'location' directives. # # Syntax: diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index 38d2d37ff8..375ef5df87 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -3221,16 +3221,16 @@ tfw_http_req_process(TfwConn *conn, const TfwFsmData *data) * - get vhost from the HTTP session information (by Sticky cookie); * - get Vhost according to TLS SNI header parsing. * But vhost returned from all that functions can be very different - * depending on HTTP chain configuration due to it flexibility and + * depending on HTTP chain configuration due to its flexibility and * client (attacker) behaviour. - * The most secure and a slow way: compare all of them and reject if - * at least some doesn't match. Can be too strict, service quality can + * The most secure and slow way: compare all of them and reject if + * at least some do not match. Can be too strict, service quality can * be degraded. * The fastest way: compute as minimum as possible: use TLS first * (anyway we use it to send right certificate), then cookie, then HTTP * chain. There can be possibility that a request will be forwarded to * a wrong server. May happen when a single certificate is used to - * speak with multiple vhosts (multy-domain (SAN) certificate as + * speak with multiple vhosts (multi-domain (SAN) certificate as * globally default TLS certificate in Tempesta config file). * The most reliable way: use the highest OSI level vhost: by HTTP * session, if no -> by http chain, if no -> by TLS conn. diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index f3fd9d71f3..e93dce749a 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -373,7 +373,7 @@ frang_conn_new(struct sock *sk) * TLS-enabled clients will use higher limits than non-TLS clients; * the same client can break security rules for one vhost, but be a * legitimate client for other vhosts, so it's a big question how to - * block him by IP or by connection resets; if multy-domain certificate + * block him by IP or by connection resets; if multi-domain certificate * (SAN) is configured, TLS clients will behave as non-TLS. Too * complicated for administrator to understand how client is blocked * and to configure it, while making some of the limits to be global @@ -822,7 +822,7 @@ frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, * recipient without buffering the entire response. * * RFC doesn't prohibit trailers in request, but it always speaks about - * trailers in response context. But request with trailer headers + * trailers in response context. But requests with trailer headers * are valid http messages. Support for them is not documented, * and implementation-dependent. E.g. Apache doesn't care about trailer * headers, but ModSecurity for Apache does. From f9bb2632b6125aa7dd7a85ab22916e2b8b1ebe06 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Fri, 26 Jul 2019 12:11:44 +0500 Subject: [PATCH 16/20] fix misspeling error --- tempesta_fw/http_limits.c | 38 +++++++++++++------------- tempesta_fw/http_limits.h | 6 +++-- tempesta_fw/http_types.h | 2 +- tempesta_fw/sock_clnt.c | 11 ++++---- tempesta_fw/vhost.c | 57 ++++++++++++++++++++------------------- tempesta_fw/vhost.h | 2 +- tls/ttls.h | 2 +- 7 files changed, 59 insertions(+), 59 deletions(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index e93dce749a..274ca0d6c8 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -686,7 +686,7 @@ enum { Frang_Req_Hdr_NoState, Frang_Req_Body_Start, - Frang_Req_Body_End, + Frang_Req_Body, Frang_Req_Body_NoState, @@ -756,7 +756,7 @@ frang_http_req_incomplete_hdrs_check(FrangAcc *ra, TfwFsmData *data, static int frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, - FrangGlobCfg *fg_cfg, FrangCfg *f_cfg) + FrangGlobCfg *fg_cfg, FrangVhostCfg *f_cfg) { TfwHttpReq *req = (TfwHttpReq *)data->req; unsigned int body_len = f_cfg->http_body_len; @@ -827,21 +827,21 @@ frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, * and implementation-dependent. E.g. Apache doesn't care about trailer * headers, but ModSecurity for Apache does. * https://swende.se/blog/HTTPChunked.html - * Some discussions also highlights that trailer headers are poorly + * Some discussions also highlight that trailer headers are poorly * supported on both servers and clients, while CDNs tend to add * trailers. https://github.com/whatwg/fetch/issues/34 * * Since RFC doesn't speak clearly about trailer headers in requests, the - * following assumptions was used: - * - Our intermediaries on client side doesn't care about trailers and send + * following assumptions were used: + * - Our intermediaries on client side do not care about trailers and send * them in the manner as the body. Thus frang's body limitations are used, * not headers ones. - * - Same header may have different values depending on how the servers works + * - Same header may have different values depending on how the servers work * with the trailer. Administrator can block that behaviour. */ static int frang_http_req_trailer_check(FrangAcc *ra, TfwFsmData *data, - FrangGlobCfg *fg_cfg, FrangCfg *f_cfg) + FrangGlobCfg *fg_cfg, FrangVhostCfg *f_cfg) { int r = TFW_PASS; TfwHttpReq *req = (TfwHttpReq *)data->req; @@ -898,7 +898,7 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, { int r = TFW_PASS; TfwHttpReq *req = (TfwHttpReq *)data->req; - FrangCfg *f_cfg = NULL; + FrangVhostCfg *f_cfg = NULL; FrangGlobCfg *fg_cfg = NULL; T_FSM_INIT(Frang_Req_0, "frang"); @@ -925,7 +925,7 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, spin_lock(&ra->lock); /* - * Detect slowris attack first, and then proceed with more precise + * Detect slowloris attack first, and then proceed with more precise * checks. This is not an FSM state, because the checks are required * every time a new request chunk is received and will be present in * every FSM state. @@ -979,8 +979,7 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, /* * Headers are not fully parsed, a new request chunk was received. - * Test that all currently received headers doesn't overcome frang - * limits. + * Test that all currently received headers do not exceed frang limits. */ T_FSM_STATE(Frang_Req_Hdr_Check) { if (test_bit(TFW_HTTP_B_FIELD_DUPENTRY, req->flags)) { @@ -996,7 +995,7 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, /* Headers are not fully parsed yet. */ if (!(req->crlf.flags & TFW_STR_COMPLETE)) - __FRANG_FSM_JUMP_EXIT(Frang_Req_Hdr_UriLen); + __FRANG_FSM_JUMP_EXIT(Frang_Req_Hdr_Check); /* * Full HTTP header has been processed, and any possible * header fields are collected. Run final checks on them. @@ -1027,17 +1026,16 @@ frang_http_req_process(FrangAcc *ra, TfwConn *conn, TfwFsmData *data, req->tm_bchunk = jiffies; r = frang_http_req_incomplete_body_check(ra, data, fg_cfg, f_cfg); - __FRANG_FSM_MOVE(Frang_Req_Body_End); + __FRANG_FSM_MOVE(Frang_Req_Body); } /* * Body is not fully parsed, a new body chunk was received. */ - T_FSM_STATE(Frang_Req_Body_End) { - + T_FSM_STATE(Frang_Req_Body) { /* Body is not fully parsed yet. */ if (!(req->body.flags & TFW_STR_COMPLETE)) - __FRANG_FSM_JUMP_EXIT(Frang_Req_Body_End); + __FRANG_FSM_JUMP_EXIT(Frang_Req_Body); __FRANG_FSM_MOVE(Frang_Req_Trailer); } @@ -1184,8 +1182,8 @@ frang_resp_fwd_process(TfwHttpResp *resp) int r; FrangAcc *ra; TfwHttpReq *req = resp->req; - FrangCfg *fcfg = req->location ? req->location->frang_cfg - : req->vhost->loc_dflt->frang_cfg; + FrangVhostCfg *fcfg = req->location ? req->location->frang_cfg + : req->vhost->loc_dflt->frang_cfg; FrangHttpRespCodeBlock *conf = fcfg->http_resp_code_block; /* @@ -1208,12 +1206,12 @@ frang_resp_fwd_process(TfwHttpResp *resp) spin_lock(&ra->lock); /* - * According to backend response code attacker may be trying to crack + * According to the backend response code attacker may be trying to crack * the password. This security event must be triggered when the response * received, and can't be triggered on request context because client * can send a huge number of pipelined requests to crack the password * and wait for the results. If the attack is spotted, block the client - * and wipe all it's received but not responded requests ASAP. + * and wipe all their received but not processed requests ASAP. */ r = frang_resp_code_limit(ra, conf); spin_unlock(&ra->lock); diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index 22590a7b03..b3c929af48 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -131,7 +131,7 @@ typedef struct { /** * Global Frang limits. As a request is received, it's not possible to determine - * it's target vhost or/and location until all the headers are parsed. Thus some + * its target vhost or/and location until all the headers are parsed. Thus some * limits can't be redefined for vhost or location and can exist only as * unique top-level limits. * @@ -168,7 +168,9 @@ struct frang_global_cfg_t { }; /** - * Vhost|location -specific Frang directives. + * Vhost and location specific Frang limits. As soon as full headers set is + * received, it's possible to determine target vhost and location. The structure + * contains full effective set of limits for chosen vhost and location. * * @http_methods_mask - Allowed HTTP request methods; * @http_uri_len - Maximum allowed URI len; diff --git a/tempesta_fw/http_types.h b/tempesta_fw/http_types.h index 30ba9b15b0..d0ec60401f 100644 --- a/tempesta_fw/http_types.h +++ b/tempesta_fw/http_types.h @@ -27,6 +27,6 @@ typedef struct tfw_http_req_t TfwHttpReq; typedef struct tfw_http_resp_t TfwHttpResp; typedef struct tfw_vhost_t TfwVhost; typedef struct frang_global_cfg_t FrangGlobCfg; -typedef struct frang_vhost_cfg_t FrangCfg; +typedef struct frang_vhost_cfg_t FrangVhostCfg; #endif /* __TFW_HTTP_TYPES_H__ */ diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 2851daf6bb..444e1377c5 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -297,9 +297,8 @@ __cli_conn_close_sync_cb(TfwConn *conn) /** * Asynchronously close all client connections. Some connection close requests - * may be lost due to workqueue overrun. So the function must be closed - * repeatedly to guarantee that all the connection was closed. - * + * may be lost due to workqueue overrun. So the function must be called + * repeatedly until 0 is returned to guarantee that all connections are closed. */ static int tfw_cli_conn_close_all(void *data) @@ -311,10 +310,10 @@ tfw_cli_conn_close_all(void *data) } /** - * Close all connections with given client, called on security events. Unlike - * asynchronous function above, this one must guarantee that all the close + * Close all connections with a given client, called on security events. Unlike + * @tfw_cli_conn_close_all(), this one must guarantee that all the close * requests will be done. Attackers can spam Tempesta with lot of requests and - * connections, trying to cause work queue overrun and delay of security events + * connections, trying to cause a work queue overrun and delay security events * handlers. To detach attackers efficiently, we have to use synchronous close. */ int tfw_cli_conn_close_all_sync(TfwClient *cli) diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index c45ef1922b..db8508fbca 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -361,7 +361,7 @@ static TfwGlobal tfw_global = { .capuacl = tfw_capuacl_dflt, }; /* Temporal structures to parse top level (outside vhost) Frang configuration. */ -static FrangCfg tfw_frang_vhost_reconfig; +static FrangVhostCfg tfw_frang_vhost_reconfig; static FrangGlobCfg tfw_frang_glob_reconfig; @@ -1016,11 +1016,11 @@ tfw_location_lookup(TfwVhost *vhost, tfw_match_t op, const char *arg, size_t len } static int -tfw_frang_cfg_inherit(FrangCfg *curr, FrangCfg *from) +tfw_frang_cfg_inherit(FrangVhostCfg *curr, FrangVhostCfg *from) { int r = 0; - memcpy(curr, from, sizeof(FrangCfg)); + memcpy(curr, from, sizeof(FrangVhostCfg)); if (from->http_ct_vals) { size_t sz = from->http_ct_vals_sz; @@ -1042,7 +1042,7 @@ tfw_frang_cfg_inherit(FrangCfg *curr, FrangCfg *from) } } if (unlikely(r)) - T_WARN_NL("Failed to inherit Frang limits: %d.", r); + T_WARN_NL("Failed to inherit Frang limits: %d.\n", r); return r; } @@ -1052,7 +1052,7 @@ tfw_location_init(TfwLocation *loc, tfw_match_t op, const char *arg, size_t len, TfwPool *pool) { char *argmem, *data; - size_t size = sizeof(FrangCfg) + size_t size = sizeof(FrangVhostCfg) + sizeof(TfwCaPolicy *) * TFW_CAPOLICY_ARRAY_SZ + sizeof(TfwNipDef *) * TFW_NIPDEF_ARRAY_SZ + sizeof(TfwHdrModsDesc) * TFW_USRHDRS_ARRAY_SZ * 2; @@ -1067,7 +1067,7 @@ tfw_location_init(TfwLocation *loc, tfw_match_t op, const char *arg, loc->op = op; loc->arg = argmem; loc->len = len; - loc->frang_cfg = (FrangCfg *)data; + loc->frang_cfg = (FrangVhostCfg *)data; loc->capo = (TfwCaPolicy **)(loc->frang_cfg + 1); loc->capo_sz = 0; loc->nipdef = (TfwNipDef **)(loc->capo + TFW_CAPOLICY_ARRAY_SZ); @@ -1216,17 +1216,17 @@ tfw_cfgop_out_location_finish(TfwCfgSpec *cs) } static void -__tfw_frang_clean(FrangCfg *cfg) +__tfw_frang_clean(FrangVhostCfg *cfg) { kfree(cfg->http_ct_vals); kfree(cfg->http_resp_code_block); } static void -tfw_frang_clean(FrangCfg *cfg) +tfw_frang_clean(FrangVhostCfg *cfg) { __tfw_frang_clean(cfg); - memset(cfg, 0, sizeof(FrangCfg)); + memset(cfg, 0, sizeof(FrangVhostCfg)); } static void @@ -1634,7 +1634,8 @@ __tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce, } static int -__tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, FrangCfg *conf) +__tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, + FrangVhostCfg *conf) { void *mem; const char *in_str; @@ -1711,7 +1712,7 @@ frang_parse_ushort(const char *s, unsigned short *out) */ static int __tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce, - FrangCfg *conf) + FrangVhostCfg *conf) { FrangHttpRespCodeBlock *cb; static const char *error_msg_begin = "frang: http_resp_code_block:"; @@ -1827,7 +1828,7 @@ tfw_cfgop_frang_body_timeout(TfwCfgSpec *cs, TfwCfgEntry *ce) return r; } -static FrangCfg * +static FrangVhostCfg * tfw_cfgop_frang_get_cfg(void) { if (tfwcfg_this_location) @@ -1841,7 +1842,7 @@ static int tfw_cfgop_frang_uri_len(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_uri_len) return 0; @@ -1855,7 +1856,7 @@ static int tfw_cfgop_frang_field_len(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_field_len) return 0; @@ -1869,7 +1870,7 @@ static int tfw_cfgop_frang_body_len(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_body_len) return 0; @@ -1883,7 +1884,7 @@ static int tfw_cfgop_frang_hdr_cnt(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_hdr_cnt) return 0; @@ -1897,7 +1898,7 @@ static int tfw_cfgop_frang_host_required(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_host_required) return 0; @@ -1911,7 +1912,7 @@ static int tfw_cfgop_frang_ct_required(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_ct_required) return 0; @@ -1925,7 +1926,7 @@ static int tfw_cfgop_frang_trailer_split(TfwCfgSpec *cs, TfwCfgEntry *ce) { int r; - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_trailer_split) return 0; @@ -1938,7 +1939,7 @@ tfw_cfgop_frang_trailer_split(TfwCfgSpec *cs, TfwCfgEntry *ce) static int tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce) { - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (ce->dflt_value && cfg->http_methods_mask) return 0; @@ -1948,7 +1949,7 @@ tfw_cfgop_frang_http_methods(TfwCfgSpec *cs, TfwCfgEntry *ce) static int tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) { - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (cfg->http_ct_vals) { if (ce->dflt_value) @@ -1963,7 +1964,7 @@ tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) static int tfw_cfgop_frang_rsp_code_block(TfwCfgSpec *cs, TfwCfgEntry *ce) { - FrangCfg *cfg = tfw_cfgop_frang_get_cfg(); + FrangVhostCfg *cfg = tfw_cfgop_frang_get_cfg(); if (cfg->http_resp_code_block) { if (ce->dflt_value) @@ -1982,9 +1983,9 @@ tfw_cfgop_http_post_validate(TfwCfgSpec *cs, TfwCfgEntry *ce) } /* - * Frang objects are cleaned when its location is destroyed. This dummy function - * is required to save time during reconfiguration by skipping traversing over - * the list of child directives cleanup functions. + * Frang objects are cleaned when their location is destroyed. This dummy + * function is required to save time during reconfiguration by skipping + * traversing over the list of child directives cleanup functions. */ static void tfw_cfgop_frang_cleanup(TfwCfgSpec *cs) @@ -2281,9 +2282,9 @@ tfw_vhost_cfgclean(void) /* * Not all Frang specs can be applied to nested locations and can be applied - * only as high-level options. It's possible to provide it's own sets for global - * and inner (location) options. But warning "line 32: the frang limit can be - * assigned only at global level" is much more user friendly than generic + * only as high-level options. It's possible to provide their own sets for + * global and inner (location) options. But warning "line 32: the frang limit + * can be assigned only at global level" is much more user friendly than generic * "line 32: unknown command". */ static TfwCfgSpec tfw_global_frang_specs[] = { diff --git a/tempesta_fw/vhost.h b/tempesta_fw/vhost.h index 92f022511d..8de0a8759b 100644 --- a/tempesta_fw/vhost.h +++ b/tempesta_fw/vhost.h @@ -116,7 +116,7 @@ typedef struct { size_t nipdef_sz; TfwCaPolicy **capo; TfwNipDef **nipdef; - FrangCfg *frang_cfg; + FrangVhostCfg *frang_cfg; TfwSrvGroup *main_sg; TfwSrvGroup *backup_sg; TfwPool *hdrs_pool; diff --git a/tls/ttls.h b/tls/ttls.h index 441ad5f2fb..e88a40ddee 100644 --- a/tls/ttls.h +++ b/tls/ttls.h @@ -893,7 +893,7 @@ void ttls_set_hs_authmode(ttls_context *ssl, int authmode); * following parameters: (void *parameter, ttls_context *ssl, * const unsigned char *hostname, size_t len). If a suitable * certificate is found, the callback must fill the - * peer tls configuration part in TLS context, + * peer tls configuration part in the TLS context, * and may optionally adjust authentication mode with * \c ttls_set_hs_authmode(), * then must return 0. If no matching name is found, the From 2b41bf3a980a0abfc7801d971806fbe1145f21c1 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Fri, 26 Jul 2019 13:17:24 +0500 Subject: [PATCH 17/20] frang: make http_body_len long --- tempesta_fw/cfg.c | 45 +++++++++++++++++++++++++++++++++++++++ tempesta_fw/cfg.h | 2 ++ tempesta_fw/http_limits.c | 2 +- tempesta_fw/http_limits.h | 2 +- tempesta_fw/vhost.c | 2 +- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/tempesta_fw/cfg.c b/tempesta_fw/cfg.c index 47ae39a788..ca18b01b6d 100644 --- a/tempesta_fw/cfg.c +++ b/tempesta_fw/cfg.c @@ -1354,6 +1354,16 @@ tfw_cfg_parse_int(const char *s, int *out_int) } EXPORT_SYMBOL(tfw_cfg_parse_int); +int +tfw_cfg_parse_long(const char *s, long *out_long) +{ + int base = detect_base(&s); + if (!base) + return -EINVAL; + return kstrtol(s, base, out_long); +} +EXPORT_SYMBOL(tfw_cfg_parse_long); + int tfw_cfg_parse_uint(const char *s, unsigned int *out_uint) { @@ -1661,6 +1671,41 @@ tfw_cfg_set_int(TfwCfgSpec *cs, TfwCfgEntry *e) } EXPORT_SYMBOL(tfw_cfg_set_int); +int +tfw_cfg_set_long(TfwCfgSpec *cs, TfwCfgEntry *e) +{ + int r; + long val, *dest_long; + TfwCfgSpecInt *cse; + + BUG_ON(!cs->dest); + + if ((r = tfw_cfg_check_single_val(e))) + goto err; + + r = tfw_cfg_parse_long(e->vals[0], &val); + if (r) + goto err; + + /* Check value restrictions if we have any in the spec extension. */ + cse = cs->spec_ext; + if (cse) { + r = tfw_cfg_check_multiple_of(val, cse->multiple_of); + r |= tfw_cfg_check_range(val, cse->range.min, cse->range.max); + if (r) + goto err; + } + + dest_long = cs->dest; + *dest_long = val; + return 0; + +err: + TFW_ERR_NL("can't parse long integer"); + return -EINVAL; +} +EXPORT_SYMBOL(tfw_cfg_set_long); + static void tfw_cfg_cleanup_str(TfwCfgSpec *cs) { diff --git a/tempesta_fw/cfg.h b/tempesta_fw/cfg.h index 55556b8c85..ad450e0735 100644 --- a/tempesta_fw/cfg.h +++ b/tempesta_fw/cfg.h @@ -404,6 +404,7 @@ enum { /* Generic TfwCfgSpec->handler functions. */ int tfw_cfg_set_bool(TfwCfgSpec *self, TfwCfgEntry *parsed_entry); int tfw_cfg_set_int(TfwCfgSpec *spec, TfwCfgEntry *parsed_entry); +int tfw_cfg_set_long(TfwCfgSpec *spec, TfwCfgEntry *parsed_entry); int tfw_cfg_set_str(TfwCfgSpec *spec, TfwCfgEntry *parsed_entry); int tfw_cfg_handle_children(TfwCfgSpec *self, TfwCfgEntry *parsed_entry); void tfw_cfg_cleanup_children(TfwCfgSpec *cs); @@ -414,6 +415,7 @@ int tfw_cfg_check_multiple_of(long value, int divisor); int tfw_cfg_check_val_n(const TfwCfgEntry *e, int val_n); int tfw_cfg_check_single_val(const TfwCfgEntry *e); int tfw_cfg_parse_int(const char *s, int *out_int); +int tfw_cfg_parse_long(const char *s, long *out_long); int tfw_cfg_parse_uint(const char *s, unsigned int *out_uint); int tfw_cfg_parse_intvl(const char *s, unsigned long *i0, unsigned long *i1); int tfw_cfg_map_enum(const TfwCfgEnum mappings[], diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 274ca0d6c8..14764b86fa 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -759,7 +759,7 @@ frang_http_req_incomplete_body_check(FrangAcc *ra, TfwFsmData *data, FrangGlobCfg *fg_cfg, FrangVhostCfg *f_cfg) { TfwHttpReq *req = (TfwHttpReq *)data->req; - unsigned int body_len = f_cfg->http_body_len; + unsigned long body_len = f_cfg->http_body_len; unsigned int bchunk_cnt = fg_cfg->http_bchunk_cnt; unsigned long body_timeout = fg_cfg->clnt_body_timeout; struct sk_buff *skb = data->skb; diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index b3c929af48..398db00694 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -186,9 +186,9 @@ struct frang_global_cfg_t { */ struct frang_vhost_cfg_t { unsigned long http_methods_mask; + unsigned long http_body_len; unsigned int http_uri_len; unsigned int http_field_len; - unsigned int http_body_len; unsigned int http_hdr_cnt; FrangCtVal *http_ct_vals; diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index db8508fbca..2771c06dcf 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -1875,7 +1875,7 @@ tfw_cfgop_frang_body_len(TfwCfgSpec *cs, TfwCfgEntry *ce) if (ce->dflt_value && cfg->http_body_len) return 0; cs->dest = &cfg->http_body_len; - r = tfw_cfg_set_int(cs, ce); + r = tfw_cfg_set_long(cs, ce); cs->dest = NULL; return r; } From 8cae88df98a6f71602861739cc19970f0a60507f Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Fri, 26 Jul 2019 17:49:00 +0500 Subject: [PATCH 18/20] frang: correctly update ct_vals pointers when the option is inherited. --- tempesta_fw/http_limits.c | 4 +-- tempesta_fw/http_limits.h | 36 ++++++++++++++++++++--- tempesta_fw/vhost.c | 61 ++++++++++++++++++++------------------- 3 files changed, 66 insertions(+), 35 deletions(-) diff --git a/tempesta_fw/http_limits.c b/tempesta_fw/http_limits.c index 14764b86fa..a3f47d0f7f 100644 --- a/tempesta_fw/http_limits.c +++ b/tempesta_fw/http_limits.c @@ -533,7 +533,7 @@ frang_http_methods(const TfwHttpReq *req, FrangAcc *ra, unsigned long m_mask) } static int -frang_http_ct_check(const TfwHttpReq *req, FrangAcc *ra, FrangCtVal *ct_vals) +frang_http_ct_check(const TfwHttpReq *req, FrangAcc *ra, FrangCtVals *ct_vals) { TfwStr field, *s; FrangCtVal *curr; @@ -569,7 +569,7 @@ frang_http_ct_check(const TfwHttpReq *req, FrangAcc *ra, FrangCtVal *ct_vals) * switch between the two if performance is critical here, * but benchmarks should be done to measure the impact. */ - for (curr = ct_vals; curr->str; ++curr) { + for (curr = ct_vals->vals; curr->str; ++curr) { if (tfw_str_eq_cstr(&field, curr->str, curr->len, TFW_STR_EQ_PREFIX_CASEI)) return TFW_PASS; diff --git a/tempesta_fw/http_limits.h b/tempesta_fw/http_limits.h index 398db00694..5e2c1e2213 100644 --- a/tempesta_fw/http_limits.h +++ b/tempesta_fw/http_limits.h @@ -124,11 +124,40 @@ typedef struct { unsigned short tf; } FrangHttpRespCodeBlock; +/** + * Single allowed Content-Type value. + * @str - pointer to allowed value; + * @len - The pre-computed strlen(@str); + */ typedef struct { - char *str; - size_t len; /* The pre-computed strlen(@str). */ + char *str; + size_t len; } FrangCtVal; +/** + * Variable-sized array of allowed Content-Type values. It's allocated by single + * memory piece to keep all the data as close as possible. + * @alloc_sz - Full size of the structure; + * @vals - Variable array of allowed values; + * @data - Variable sized data area where @vals points to. + * + * Basically that will look like: + * [@vals ][@data ] + * [FrangCtVal, FrangCtVal, FrangCtVal, NULL][str1\0\str2\0\str3\0] + * + + + ^ ^ ^ + * | | | | | | + * +----------------------------------+ | | + * | | | | + * +-------------------------------+ | + * | | + * +----------------------------+ + */ +typedef struct { + size_t alloc_sz; + FrangCtVal *vals; + char *data; +} FrangCtVals; + /** * Global Frang limits. As a request is received, it's not possible to determine * its target vhost or/and location until all the headers are parsed. Thus some @@ -191,8 +220,7 @@ struct frang_vhost_cfg_t { unsigned int http_field_len; unsigned int http_hdr_cnt; - FrangCtVal *http_ct_vals; - size_t http_ct_vals_sz; + FrangCtVals *http_ct_vals; FrangHttpRespCodeBlock *http_resp_code_block; bool http_ct_required; diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index 2771c06dcf..110f854ded 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -1016,23 +1016,33 @@ tfw_location_lookup(TfwVhost *vhost, tfw_match_t op, const char *arg, size_t len } static int -tfw_frang_cfg_inherit(FrangVhostCfg *curr, FrangVhostCfg *from) +tfw_frang_cfg_inherit(FrangVhostCfg *curr, const FrangVhostCfg *from) { int r = 0; memcpy(curr, from, sizeof(FrangVhostCfg)); if (from->http_ct_vals) { - size_t sz = from->http_ct_vals_sz; - curr->http_ct_vals = kmalloc(sz, GFP_KERNEL); - if (!curr->http_ct_vals) + size_t sz = from->http_ct_vals->alloc_sz; + FrangCtVal *val; + long delta; + + curr->http_ct_vals = kzalloc(sz, GFP_KERNEL); + if (!curr->http_ct_vals) { r = -ENOMEM; - else - memcpy(curr->http_ct_vals, from->http_ct_vals, sz); + } + delta = (void *)from->http_ct_vals - (void *)curr->http_ct_vals; + memcpy(curr->http_ct_vals, from->http_ct_vals, sz); + curr->http_ct_vals->vals = (void *)curr->http_ct_vals + + sizeof(FrangCtVals); + curr->http_ct_vals->data -= delta; + /* Restore data pointers. */ + for (val = curr->http_ct_vals->vals; val->str; ++ val) + val->str -= delta; } if (!r && from->http_resp_code_block) { size_t sz = sizeof(FrangHttpRespCodeBlock); - curr->http_resp_code_block = kmalloc(sz, GFP_KERNEL); + curr->http_resp_code_block = kzalloc(sz, GFP_KERNEL); if (!curr->http_resp_code_block) { r = -ENOMEM; } @@ -1639,22 +1649,14 @@ __tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, { void *mem; const char *in_str; - char *strs, *strs_pos; - FrangCtVal *vals, *vals_pos; - size_t i, strs_size, vals_n, vals_size; + char *strs_pos; + FrangCtVals *vals; + FrangCtVal *vals_pos; + size_t i, strs_size, vals_n, vals_size, alloc_sz; /* Allocate a single chunk of memory which is suitable to hold the - * variable-sized list of variable-sized strings. - * - * Basically that will look like: - * [[FrangCtVal, FrangCtVal, FrangCtVal, NULL]str1\0\str2\0\str3\0] - * + + + ^ ^ ^ - * | | | | | | - * +---------------------------------+ | | - * | | | | - * +------------------------------+ | - * | | - * +---------------------------+ + * variable-sized list of variable-sized strings. See FrangCtVals + * definition for details. */ vals_n = ce->val_n; vals_size = sizeof(FrangCtVal) * (vals_n + 1); @@ -1662,16 +1664,19 @@ __tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, TFW_CFG_ENTRY_FOR_EACH_VAL(ce, i, in_str) { strs_size += strlen(in_str) + 1; } - mem = kzalloc(vals_size + strs_size, GFP_KERNEL); + alloc_sz = vals_size + strs_size + sizeof(FrangCtVals); + mem = kzalloc(alloc_sz, GFP_KERNEL); if (!mem) return -ENOMEM; vals = mem; - strs = mem + vals_size; + vals->alloc_sz = alloc_sz; + vals->vals = mem + sizeof(FrangCtVals); + vals->data = mem + sizeof(FrangCtVals) + vals_size; /* Copy tokens to the new vals/strs list. */ /* TODO: validate tokens, they should look like: "text/plain". */ - vals_pos = vals; - strs_pos = strs; + vals_pos = vals->vals; + strs_pos = vals->data; TFW_CFG_ENTRY_FOR_EACH_VAL(ce, i, in_str) { size_t len = strlen(in_str) + 1; @@ -1684,11 +1689,10 @@ __tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce, vals_pos++; strs_pos += len; } - BUG_ON(vals_pos != (vals + vals_n)); - BUG_ON(strs_pos != (strs + strs_size)); + BUG_ON(vals_pos != (vals->vals + vals_n)); + BUG_ON(strs_pos != (vals->data + strs_size)); conf->http_ct_vals = vals; - conf->http_ct_vals_sz = vals_size + strs_size; return 0; } @@ -1956,7 +1960,6 @@ tfw_cfgop_frang_http_ct_vals(TfwCfgSpec *cs, TfwCfgEntry *ce) return 0; kfree(cfg->http_ct_vals); cfg->http_ct_vals = NULL; - cfg->http_ct_vals_sz = 0; } return __tfw_cfgop_frang_http_ct_vals(cs, ce, cfg); } From e0e1fb70d9e59666b822c722f5e2196efb8183df Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Wed, 31 Jul 2019 12:27:59 +0500 Subject: [PATCH 19/20] frang: fix inheriting frang configuration --- tempesta_fw/vhost.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tempesta_fw/vhost.c b/tempesta_fw/vhost.c index 110f854ded..12da51f10e 100644 --- a/tempesta_fw/vhost.c +++ b/tempesta_fw/vhost.c @@ -1569,9 +1569,8 @@ tfw_vhost_new(const char *name) return NULL; } if (strcasecmp(name, TFW_VH_DFT_NAME)) { - TfwVhost *dvh = tfw_vhosts_reconfig->vhost_dflt; if (tfw_frang_cfg_inherit(vhost->loc_dflt->frang_cfg, - dvh->loc_dflt->frang_cfg)) + &tfw_frang_vhost_reconfig)) { tfw_vhost_destroy(vhost); return NULL; From 140f5ddb58fa694d269d469338b7a85fd7c6da73 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Thu, 1 Aug 2019 02:48:50 +0500 Subject: [PATCH 20/20] hm: assign host and location to HM request to correctly address policies --- tempesta_fw/http.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index 375ef5df87..bfd480d306 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -3980,6 +3980,7 @@ tfw_http_hm_srv_send(TfwServer *srv, char *data, unsigned long len) .len = len, }; LIST_HEAD(equeue); + bool block = false; if (!(req = tfw_http_msg_alloc_req_light())) return; @@ -3992,6 +3993,28 @@ tfw_http_hm_srv_send(TfwServer *srv, char *data, unsigned long len) __set_bit(TFW_HTTP_B_HMONITOR, req->flags); req->jrxtstamp = jiffies; + /* + * Vhost and location store policies definitions that can be + * required on various stages of request-response processing. + * E.g. response to HM request still needs to be processed by frang, + * and vhost keeps the frang configuration. + * + * The request is created using lightweight function, req->uri_path + * is not set, thus default location is used. + * + * TBD: it's more natural to configure HM not in server group section, + * but in vhost: instead of table lookups target vhost could be chosen + * directly. + */ + req->vhost = tfw_http_tbl_vhost((TfwMsg *)req, &block); + if (unlikely(!req->vhost || block)) { + TFW_WARN_ADDR("Unable to assign vhost for health monitoring " + "request of backend server", &srv->addr, + TFW_WITH_PORT); + goto cleanup; + } + req->location = req->vhost->loc_dflt; + srv_conn = srv->sg->sched->sched_srv_conn((TfwMsg *)req, srv); if (!srv_conn) { TFW_WARN_ADDR("Unable to find connection for health"