diff --git a/doc/configuration.txt b/doc/configuration.txt index 5e385b761..a895caaae 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -6001,6 +6001,7 @@ external-check path X - X X force-persist - - X X force-be-switch - X X - filter - X X X +filter-sequence - X X X fullconn X - X X guid - X X X hash-balance-factor X - X X @@ -7891,8 +7892,41 @@ filter [param*] server srv1 192.168.0.1:80 - See also : section 9. + See also : section 9., "filter-sequence" +filter-sequence { request | response } + + Specifies in which order filters declared on the proxy should be + executed. + + May be used in the following contexts: tcp, http + + May be used in sections: defaults | frontend | listen | backend + no | yes | yes | yes + + Comma-separated list of filter names () to specify in which + order filters declared on the proxy should be executed, for request + or response path, respectively. + + When filter-sequence is not specified for a given path (ie: request vs + response), the order in which filters are declared on the proxy is used. + + If filter-sequence omits some filters that were declared on the proxy, + they will not be executed. This is an effective way of temporarily + disabling a filter without removing it from the configuration. + + Example: + global + lua-load my-filter.lua # defines custom "lua.my-filter" + frontend myfront + filter comp-req + filter comp-res + filter lua.my-filter + + filter-sequence request lua.my-filter,comp-req + filter-sequence response lua.my-filter,comp-res + + See also : "filter" fullconn Specify at what backend load the servers will reach their maxconn diff --git a/include/haproxy/filters-t.h b/include/haproxy/filters-t.h index f5a49856b..317c46a9b 100644 --- a/include/haproxy/filters-t.h +++ b/include/haproxy/filters-t.h @@ -215,6 +215,12 @@ struct flt_conf { unsigned int flags; /* FLT_CFG_FL_* */ }; +struct filter_sequence_elt { + char *flt_name; /* filter name (set during parsing) */ + struct flt_conf *flt_conf; /* associated filter conf (set after parsing) */ + struct list list; /* list element */ +}; + /* * Structure reprensenting a filter instance attached to a stream * diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 5e786da3e..8914dc5ce 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -509,6 +509,12 @@ struct proxy { * name is used */ struct list filter_configs; /* list of the filters that are declared on this proxy */ + struct { /* sequence in which declared filters on the proxy should be execute + * (list of filter_sequence_elt) + */ + struct list req; /* during request handling */ + struct list res; /* during response handling */ + } filter_sequence; struct guid_node guid; /* GUID global tree node */ struct mt_list watcher_list; /* list of elems which currently references this proxy instance (currently only used with backends) */ diff --git a/src/filters.c b/src/filters.c index 61fcc8fe2..fc6006309 100644 --- a/src/filters.c +++ b/src/filters.c @@ -297,6 +297,136 @@ parse_filter(char **args, int section_type, struct proxy *curpx, } +/* + * Parses the "filter-sequence" keyword + */ +static int +parse_filter_sequence(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, char **err) +{ + /* filter-sequence cannot be defined on a default proxy */ + if (curpx == defpx) { + memprintf(err, "parsing [%s:%d] : %s is not allowed in a 'default' section.", + file, line, args[0]); + return -1; + } + if (strcmp(args[0], "filter-sequence") == 0) { + struct list *list; + char *str; + size_t cur_sep; + + if (!*args[1]) { + memprintf(err, + "parsing [%s:%d] : missing argument for '%s' in %s '%s'.", + file, line, args[0], proxy_type_str(curpx), curpx->id); + goto error; + } + + if (!strcmp(args[1], "request")) + list = &curpx->filter_sequence.req; + else if (!strcmp(args[1], "response")) + list = &curpx->filter_sequence.res; + else { + memprintf(err, + "parsing [%s:%d] : expected either 'request' or 'response' for '%s' in %s '%s'.", + file, line, args[0], proxy_type_str(curpx), curpx->id); + goto error; + } + + if (!*args[2]) { + memprintf(err, + "parsing [%s:%d] : missing filter list for '%s' in %s '%s'.", + file, line, args[0], proxy_type_str(curpx), curpx->id); + goto error; + } + + str = args[2]; + while (str[0]) { + struct filter_sequence_elt *elt; + + elt = calloc(1, sizeof(*elt)); + if (!elt) { + memprintf(err, "'%s %s' : out of memory", args[0], args[1]); + goto error; + } + + cur_sep = strcspn(str, ","); + + elt->flt_name = my_strndup(str, cur_sep); + if (!elt->flt_name) { + ha_free(&elt); + goto error; + } + + LIST_APPEND(list, &elt->list); + + if (str[cur_sep]) + str += cur_sep + 1; + else + str += cur_sep; + } + } + + return 0; + + error: + return -1; +} + +static int compile_filter_sequence_elt(struct proxy *px, struct filter_sequence_elt *elt, char **errmsg) +{ + struct flt_conf *fconf; + int ret = ERR_NONE; + + list_for_each_entry(fconf, &px->filter_configs, list) { + if (!strcmp(elt->flt_name, fconf->name)) { + elt->flt_conf = fconf; + break; + } + } + if (!elt->flt_conf) { + memprintf(errmsg, "invalid filter name: '%s' is not defined on the proxy", elt->flt_name); + ret = ERR_FATAL; + } + + return ret; +} + +/* after config is checked, time to resolve filter-sequence (both request and response) + * used on the proxy in order to associate filter names with valid flt_conf entries + * this will help decrease filter lookup time during runtime (filter ids are compared + * using their address, not string content) + */ +static int postcheck_filter_sequence(struct proxy *px) +{ + struct filter_sequence_elt *elt; + char *errmsg = NULL; + int ret = ERR_NONE; + + list_for_each_entry(elt, &px->filter_sequence.req, list) { + ret = compile_filter_sequence_elt(px, elt, &errmsg); + if (ret & ERR_CODE) { + memprintf(&errmsg, "error while postparsing request filter-sequence '%s' : %s", elt->flt_name, errmsg); + goto error; + } + } + list_for_each_entry(elt, &px->filter_sequence.res, list) { + ret = compile_filter_sequence_elt(px, elt, &errmsg); + if (ret & ERR_CODE) { + memprintf(&errmsg, "error while postparsing response filter-sequence '%s' : %s", elt->flt_name, errmsg); + goto error; + } + } + + return ret; + + error: + ha_alert("%s: %s\n", px->id, errmsg); + ha_free(&errmsg); + return ret; +} +REGISTER_POST_PROXY_CHECK(postcheck_filter_sequence); + /* * Calls 'init' callback for all filters attached to a proxy. This happens after * the configuration parsing. Filters can finish to fill their config. Returns @@ -401,6 +531,7 @@ void flt_deinit(struct proxy *proxy) { struct flt_conf *fconf, *back; + struct filter_sequence_elt *fsequence, *fsequenceb; list_for_each_entry_safe(fconf, back, &proxy->filter_configs, list) { if (fconf->ops->deinit) @@ -408,6 +539,16 @@ flt_deinit(struct proxy *proxy) LIST_DELETE(&fconf->list); free(fconf); } + list_for_each_entry_safe(fsequence, fsequenceb, &proxy->filter_sequence.req, list) { + LIST_DEL_INIT(&fsequence->list); + ha_free(&fsequence->flt_name); + ha_free(&fsequence); + } + list_for_each_entry_safe(fsequence, fsequenceb, &proxy->filter_sequence.res, list) { + LIST_DEL_INIT(&fsequence->list); + ha_free(&fsequence->flt_name); + ha_free(&fsequence); + } } /* @@ -438,7 +579,7 @@ flt_deinit_all_per_thread() /* Attaches a filter to a stream. Returns -1 if an error occurs, 0 otherwise. */ static int -flt_stream_add_filter(struct stream *s, struct flt_conf *fconf, unsigned int flags) +flt_stream_add_filter(struct stream *s, struct proxy *px, struct flt_conf *fconf, unsigned int flags) { struct filter *f; @@ -461,18 +602,38 @@ flt_stream_add_filter(struct stream *s, struct flt_conf *fconf, unsigned int fla } LIST_APPEND(&strm_flt(s)->filters, &f->list); + LIST_INIT(&f->req_list); + LIST_INIT(&f->res_list); - /* for now f->req_list == f->res_list to preserve - * historical behavior, but the ordering will change - * in the future - */ - LIST_APPEND(&s->req.flt.filters, &f->req_list); - LIST_APPEND(&s->res.flt.filters, &f->res_list); + /* use filter config ordering unless filter-sequence says otherwise */ + if (LIST_ISEMPTY(&px->filter_sequence.req)) + LIST_APPEND(&s->req.flt.filters, &f->req_list); + if (LIST_ISEMPTY(&px->filter_sequence.res)) + LIST_APPEND(&s->res.flt.filters, &f->res_list); strm_flt(s)->flags |= STRM_FLT_FL_HAS_FILTERS; return 0; } +static void flt_stream_organize_filters(struct stream *s, struct proxy *px) +{ + struct filter_sequence_elt *fsequence; + struct filter *filter; + + list_for_each_entry(fsequence, &px->filter_sequence.req, list) { + list_for_each_entry(filter, &strm_flt(s)->filters, list) { + if (filter->config == fsequence->flt_conf && !LIST_INLIST(&filter->req_list)) + LIST_APPEND(&s->req.flt.filters, &filter->req_list); + } + } + list_for_each_entry(fsequence, &px->filter_sequence.res, list) { + list_for_each_entry(filter, &strm_flt(s)->filters, list) { + if (filter->config == fsequence->flt_conf && !LIST_INLIST(&filter->res_list)) + LIST_APPEND(&s->res.flt.filters, &filter->res_list); + } + } +} + /* * Called when a stream is created. It attaches all frontend filters to the * stream. Returns -1 if an error occurs, 0 otherwise. @@ -489,9 +650,10 @@ flt_stream_init(struct stream *s) memset(&s->res.flt, 0, sizeof(s->res.flt)); LIST_INIT(&s->res.flt.filters); list_for_each_entry(fconf, &strm_fe(s)->filter_configs, list) { - if (flt_stream_add_filter(s, fconf, 0) < 0) + if (flt_stream_add_filter(s, strm_fe(s), fconf, 0) < 0) return -1; } + flt_stream_organize_filters(s, strm_fe(s)); return 0; } @@ -605,10 +767,12 @@ flt_set_stream_backend(struct stream *s, struct proxy *be) goto end; list_for_each_entry(fconf, &be->filter_configs, list) { - if (flt_stream_add_filter(s, fconf, FLT_FL_IS_BACKEND_FILTER) < 0) + if (flt_stream_add_filter(s, be, fconf, FLT_FL_IS_BACKEND_FILTER) < 0) return -1; } + flt_stream_organize_filters(s, be); + end: list_for_each_entry(filter, &strm_flt(s)->filters, list) { if (FLT_OPS(filter)->stream_set_backend) { @@ -1231,6 +1395,7 @@ handle_analyzer_result(struct stream *s, struct channel *chn, * not enabled. */ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_LISTEN, "filter", parse_filter }, + { CFG_LISTEN, "filter-sequence", parse_filter_sequence }, { 0, NULL, NULL }, } }; diff --git a/src/proxy.c b/src/proxy.c index 8dd10fb6f..c826397bf 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -1578,6 +1578,8 @@ void init_new_proxy(struct proxy *p) LIST_INIT(&p->conf.lf_checks); LIST_INIT(&p->filter_configs); LIST_INIT(&p->tcpcheck.preset_vars); + LIST_INIT(&p->filter_sequence.req); + LIST_INIT(&p->filter_sequence.res); MT_LIST_INIT(&p->lbprm.lb_free_list);