From: Christopher Faulet Date: Wed, 30 Sep 2026 16:10:22 +0000 (+0200) Subject: MEDIUM: filters: Parse and finalize filter-config instances X-Git-Tag: v3.5-dev9~89 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=b3075cf7fcbc0e39d971bdc55b951f8e3ea52845;p=haproxy.git MEDIUM: filters: Parse and finalize filter-config instances Add filter-config with a class, optional id, enabled marker and raw class arguments. Require identifiers for classes allowing multiple instances, reject duplicate local declarations, and allow inherited or implicit ones to be replaced locally. After configuration parsing, validate class multiplicity, call each class parser once per instance and flatten the per-side class trees. Extend check, init, per-thread and deinit callbacks to the finalized instances, without processing instances present on both sides twice. Let FCGI lookup find finalized instances when no legacy filter is configured. Legacy filters and instances coexist, and the stream evaluation path is not switched to instances by this patch. The enabled state is recorded but runtime attachment and legacy/instance selection remain to be implemented. --- diff --git a/src/fcgi-app.c b/src/fcgi-app.c index 29f355a87..a9fb115b7 100644 --- a/src/fcgi-app.c +++ b/src/fcgi-app.c @@ -84,12 +84,21 @@ struct fcgi_app *fcgi_app_find_by_name(const char *name) struct fcgi_flt_conf *find_px_fcgi_conf(struct proxy *px) { + struct filter_instance *inst; struct flt_conf *fconf; list_for_each_entry(fconf, &px->filter_configs, list) { if (fconf->id == fcgi_flt_id) return fconf->conf; } + + /* no legacy filter found, look for a finalized fcgi filter instance + * (instances are not finalized during the configuration parsing, in + * that case they are skipped) + */ + inst = flt_find_instance(px, filter_find_class(fcgi_filter_cls_name), NULL); + if (inst && inst->fconf) + return inst->fconf->conf; return NULL; } diff --git a/src/filters.c b/src/filters.c index 1c4da4825..654219b17 100644 --- a/src/filters.c +++ b/src/filters.c @@ -372,16 +372,39 @@ parse_filter(char **args, int section_type, struct proxy *curpx, * Calls 'init' callback for all filters attached to a proxy. This happens after * the configuration parsing. Filters can finish to fill their config. Returns * (ERR_ALERT|ERR_FATAL) if an error occurs, 0 otherwise. + * + * The callback is called for the legacy filters (px->filter_configs) and + * for the finalized filter instances (inst->fconf). Nothing is shared + * between the legacy filters and the instances. When the instances are + * attached to the evaluation path, the legacy mode or the instances mode + * will be chosen and only the corresponding list will be iterated here. */ static int flt_init(struct proxy *proxy) { + struct filter_instance *inst; struct flt_conf *fconf; list_for_each_entry(fconf, &proxy->filter_configs, list) { if (fconf->ops->init && fconf->ops->init(proxy, fconf) < 0) return ERR_ALERT|ERR_FATAL; } + list_for_each_entry(inst, &proxy->filter_req_instances, req.list) { + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->init && fconf->ops->init(proxy, fconf) < 0) + return ERR_ALERT|ERR_FATAL; + } + list_for_each_entry(inst, &proxy->filter_res_instances, res.list) { + if (LIST_INLIST(&inst->req.list)) + continue; /* already handled from the request side */ + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->init && fconf->ops->init(proxy, fconf) < 0) + return ERR_ALERT|ERR_FATAL; + } return 0; } @@ -389,16 +412,39 @@ flt_init(struct proxy *proxy) * Calls 'init_per_thread' callback for all filters attached to a proxy for each * threads. This happens after the thread creation. Filters can finish to fill * their config. Returns (ERR_ALERT|ERR_FATAL) if an error occurs, 0 otherwise. + * + * The callback is called for the legacy filters (px->filter_configs) and + * for the finalized filter instances (inst->fconf). Nothing is shared + * between the legacy filters and the instances. When the instances are + * attached to the evaluation path, the legacy mode or the instances mode + * will be chosen and only the corresponding list will be iterated here. */ static int flt_init_per_thread(struct proxy *proxy) { + struct filter_instance *inst; struct flt_conf *fconf; list_for_each_entry(fconf, &proxy->filter_configs, list) { if (fconf->ops->init_per_thread && fconf->ops->init_per_thread(proxy, fconf) < 0) return ERR_ALERT|ERR_FATAL; } + list_for_each_entry(inst, &proxy->filter_req_instances, req.list) { + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->init_per_thread && fconf->ops->init_per_thread(proxy, fconf) < 0) + return ERR_ALERT|ERR_FATAL; + } + list_for_each_entry(inst, &proxy->filter_res_instances, res.list) { + if (LIST_INLIST(&inst->req.list)) + continue; /* already handled from the request side */ + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->init_per_thread && fconf->ops->init_per_thread(proxy, fconf) < 0) + return ERR_ALERT|ERR_FATAL; + } return 0; } @@ -450,6 +496,11 @@ flt_init_all_per_thread() * after the configuration parsing but before filters initialization. Returns * the number of encountered errors. */ +/* Note: for now, only the legacy filters (px->filter_configs) are handled + * here. When the filter instances are attached to the evaluation path, we + * will have to choose between the legacy mode and the instances mode, and + * iterate the right list here. Do not forget! + */ int flt_check(struct proxy *proxy) { @@ -465,13 +516,52 @@ flt_check(struct proxy *proxy) return err; } +/* Calls 'check' callback for all finalized filter instances of the proxy + * , during the post-proxy-check stage (the instances are not + * finalized yet during the configuration checks, see flt_check()). Returns + * a combination of ERR_* flags, ERR_NONE on success. + */ +static int flt_check_instances(struct proxy *px) +{ + struct filter_instance *inst; + struct flt_conf *fconf; + int err = 0; + + list_for_each_entry(inst, &px->filter_req_instances, req.list) { + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->check) + err += fconf->ops->check(px, fconf); + } + list_for_each_entry(inst, &px->filter_res_instances, res.list) { + if (LIST_INLIST(&inst->req.list)) + continue; /* already handled from the request side */ + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->check) + err += fconf->ops->check(px, fconf); + } + if (err) + err = ERR_ALERT | ERR_FATAL; + return err; +} + /* - * Calls 'denit' callback for all filters attached to a proxy. This happens when - * HAProxy is stopped. + * Calls 'deinit' callback for all filters attached to a proxy. This happens + * when HAProxy is stopped. + * + * The callback is called for the legacy filters (px->filter_configs) and + * for the finalized filter instances (inst->fconf). Nothing is shared + * between the legacy filters and the instances. When the instances are + * attached to the evaluation path, the legacy mode or the instances mode + * will be chosen and only the corresponding list will be iterated here. */ void flt_deinit(struct proxy *proxy) { + struct filter_instance *inst; struct flt_conf *fconf, *back; list_for_each_entry_safe(fconf, back, &proxy->filter_configs, list) { @@ -480,22 +570,57 @@ flt_deinit(struct proxy *proxy) LIST_DELETE(&fconf->list); free(fconf); } + list_for_each_entry(inst, &proxy->filter_req_instances, req.list) { + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->deinit) + fconf->ops->deinit(proxy, fconf); + } + list_for_each_entry(inst, &proxy->filter_res_instances, res.list) { + if (LIST_INLIST(&inst->req.list)) + continue; /* already handled from the request side */ + fconf = inst->fconf; + if (fconf && fconf->ops && fconf->ops->deinit) + fconf->ops->deinit(proxy, fconf); + } flt_free_instances(proxy); } /* - * Calls 'denit_per_thread' callback for all filters attached to a proxy for + * Calls 'deinit_per_thread' callback for all filters attached to a proxy for * each threads. This happens before exiting a thread. + * + * The callback is called for the legacy filters (px->filter_configs) and + * for the finalized filter instances (inst->fconf), iterating the flat + * per-side lists of the proxy. */ void flt_deinit_per_thread(struct proxy *proxy) { + struct filter_instance *inst; struct flt_conf *fconf, *back; list_for_each_entry_safe(fconf, back, &proxy->filter_configs, list) { if (fconf->ops->deinit_per_thread) fconf->ops->deinit_per_thread(proxy, fconf); } + list_for_each_entry(inst, &proxy->filter_req_instances, req.list) { + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->deinit_per_thread) + fconf->ops->deinit_per_thread(proxy, fconf); + } + list_for_each_entry(inst, &proxy->filter_res_instances, res.list) { + if (LIST_INLIST(&inst->req.list)) + continue; /* already handled from the request side */ + fconf = inst->fconf; + if (!fconf) + continue; + if (fconf->ops->deinit_per_thread) + fconf->ops->deinit_per_thread(proxy, fconf); + } } @@ -1608,6 +1733,147 @@ int flt_copy_instances(struct proxy *px, const struct proxy *defpx) return -1; } +/* + * Parses the "filter-config" keyword. A filter instance is created for the + * given filter class and attached to the current proxy. The instance + * arguments are only copied here, they will be finalized during the + * post-check stage. The syntax is: + * + * filter-config [ id ] [enabled] args... + * + * The "id" and "enabled" tokens, if set, must be the first options, in that + * order. The id is optional, but it is mandatory for filter classes allowing + * several instances per proxy. A instance inherited from a defaults + * section or coming from an implicit declaration (e.g. use-fcgi-app) may be + * overridden by a instance with the same class and id, but duplicating an + * explicit instance inside the same proxy is rejected. + */ +static int parse_filter_config(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, char **err) +{ + struct filter_instance *inst, *old_inst; + struct filter_class *cls; + const char *id = NULL; + const char *err2; + int enabled = 0; + int cur_arg = 1; + int i; + + inst = NULL; + if (!*args[cur_arg]) { + memprintf(err, + "parsing [%s:%d] : missing argument for '%s' in %s '%s'.", + file, line, args[0], proxy_type_str(curpx), curpx->id); + goto error; + } + + cls = filter_find_class(args[cur_arg]); + if (!cls) { + memprintf(err, + "parsing [%s:%d] : '%s' : unknown filter class '%s'.", + file, line, args[0], args[cur_arg]); + goto error; + } + cur_arg++; + + /* optional "id " and "enabled" tokens, in that order */ + if (strcmp(args[cur_arg], "id") == 0) { + if (!*args[cur_arg+1]) { + memprintf(err, + "parsing [%s:%d] : '%s %s' : missing filter instance id.", + file, line, args[0], args[1]); + goto error; + } + id = args[cur_arg+1]; + err2 = invalid_prefix_char(id); + if (err2) { + memprintf(err, + "parsing [%s:%d] : '%s %s' : invalid character '%c' in filter instance id '%s'.", + file, line, args[0], args[1], *err2, id); + goto error; + } + cur_arg += 2; + } + if (strcmp(args[cur_arg], "enabled") == 0) { + enabled = 1; + cur_arg++; + } + + if ((cls->flags & FLT_CLS_FL_MULTI) && !id) { + memprintf(err, + "parsing [%s:%d] : '%s %s' : missing filter instance id, several instances are allowed for this filter class.", + file, line, args[0], args[1]); + goto error; + } + + inst = calloc(1, sizeof(*inst)); + if (!inst) + goto oom; + + inst->class = cls; + inst->px = curpx; + inst->enabled = enabled; + inst->flags = 0; /* explicit declaration */ + + if (id) { + inst->id = strdup(id); + if (!inst->id) + goto oom; + } + inst->conf.file = strdup(file); + if (!inst->conf.file) + goto oom; + inst->conf.line = line; + + /* copy the instance arguments, they will be finalized later */ + for (i = cur_arg; *args[i]; i++); + + inst->conf.argc = i - cur_arg; + inst->conf.argv = calloc(inst->conf.argc + 1, sizeof(*inst->conf.argv)); + if (!inst->conf.argv) + goto oom; + for (i = 0; i < inst->conf.argc; i++) { + inst->conf.argv[i] = strdup(args[cur_arg + i]); + if (!inst->conf.argv[i]) + goto oom; + } + + /* A instance inherited from a defaults section or coming from an + * implicit declaration is overridden by the new one (last wins). But + * an explicit instance declared in this same proxy cannot be + * duplicated. + */ + old_inst = flt_find_instance(curpx, cls, id); + if (old_inst) { + if (!(old_inst->flags & (FLT_INST_F_INHERITED|FLT_INST_F_IMPLICIT))) { + memprintf(err, + "'%s %s%s%s%s' : duplicate filter instance, already declared at %s:%d.", + args[0], args[1], + id ? " id " : "", id ? id : "", enabled ? " enabled" : "", + old_inst->conf.file, old_inst->conf.line); + goto error; + } + flt_free_instance(old_inst); + } + + if (flt_init_instance(inst, curpx) < 0) { + memprintf(err, + "parsing [%s:%d] : '%s %s' : filter class reference not found in %s '%s'.", + file, line, args[0], args[1], proxy_type_str(curpx), curpx->id); + goto error; + } + return 0; + + error: + if (inst) + flt_free_instance(inst); + return -1; + + oom: + memprintf(err, "parsing [%s:%d] : '%s' : out of memory", file, line, args[0]); + goto error; +} + /* Note: must not be declared as its list will be overwritten. * Please take care of keeping this list alphabetically sorted, doing so helps @@ -1617,6 +1883,7 @@ int flt_copy_instances(struct proxy *px, const struct proxy *defpx) * not enabled. */ static struct cfg_kw_list cfg_kws = {ILH, { { CFG_LISTEN, "filter", parse_filter }, + { CFG_LISTEN, "filter-config", parse_filter_config }, { 0, NULL, NULL }, } }; @@ -1624,6 +1891,210 @@ static struct cfg_kw_list cfg_kws = {ILH, { INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); +/* Moves the instance from the class references tree to the flat + * per-side list of the proxy , recursively: the instances reordered + * to be executed before are moved first, then itself and + * finally the instances reordered to be executed after it. + */ +static void flt_flatten_instance(struct proxy *px, struct filter_instance *inst, unsigned int side) +{ + struct filter_instance *d, *back; + + if (side == FLT_SIDE_REQ) { + list_for_each_entry_safe(d, back, &inst->req.reordered_before, req.list) + flt_flatten_instance(px, d, side); + LIST_DEL_INIT(&inst->req.list); + LIST_APPEND(&px->filter_req_instances, &inst->req.list); + list_for_each_entry_safe(d, back, &inst->req.reordered_after, req.list) + flt_flatten_instance(px, d, side); + } + else { + list_for_each_entry_safe(d, back, &inst->res.reordered_before, res.list) + flt_flatten_instance(px, d, side); + LIST_DEL_INIT(&inst->res.list); + LIST_APPEND(&px->filter_res_instances, &inst->res.list); + list_for_each_entry_safe(d, back, &inst->res.reordered_after, res.list) + flt_flatten_instance(px, d, side); + } +} + +/* Moves the instances of the list to the flat per-side list of the + * proxy , in evaluation order (see flt_flatten_instance()). + */ +static void flt_flatten_inst_list(struct proxy *px, struct list *instances, unsigned int side) +{ + struct filter_instance *inst, *back; + + if (side == FLT_SIDE_REQ) { + list_for_each_entry_safe(inst, back, instances, req.list) + flt_flatten_instance(px, inst, side); + } + else { + list_for_each_entry_safe(inst, back, instances, res.list) + flt_flatten_instance(px, inst, side); + } +} + +/* Flattens the filter instances of the proxy : the class references + * tree is consumed to produce the flat per-side lists of the proxy, in + * evaluation order. The class references and the consumed filter-enable and + * filter-sequence entries are released. + */ +static void flt_flatten_instances(struct proxy *px) +{ + struct filter_class_ref *ref, *refback; + + list_for_each_entry_safe(ref, refback, &px->conf.filter_classes_req, list) { + flt_flatten_inst_list(px, &ref->reordered_before, FLT_SIDE_REQ); + flt_flatten_inst_list(px, &ref->instances, FLT_SIDE_REQ); + flt_flatten_inst_list(px, &ref->reordered_after, FLT_SIDE_REQ); + LIST_DELETE(&ref->list); + free(ref); + } + list_for_each_entry_safe(ref, refback, &px->conf.filter_classes_res, list) { + flt_flatten_inst_list(px, &ref->reordered_before, FLT_SIDE_RES); + flt_flatten_inst_list(px, &ref->instances, FLT_SIDE_RES); + flt_flatten_inst_list(px, &ref->reordered_after, FLT_SIDE_RES); + LIST_DELETE(&ref->list); + free(ref); + } + +} + +/* Post-parses the filter instances of the proxy . For each + * instance, some sanity checks are performed and the ->parse() callback of + * the filter class is called to produce the filter configuration + * (inst->fconf). This happens after the configuration parsing, during the + * post-parsing stage. Returns a combination of ERR_* flags, ERR_NONE on + * success. + */ +static int flt_precheck_instance(struct proxy *proxy, struct filter_instance *inst) +{ + char *err = NULL; + + if (!inst->class->parse) { + ha_alert("config: %s '%s' : filter class '%s' does not support 'filter-config' (instance from %s:%d).\n", + proxy_type_str(proxy), proxy->id, inst->class->name, + inst->conf.file, inst->conf.line); + return ERR_ALERT | ERR_FATAL; + } + + inst->fconf = calloc(1, sizeof(*inst->fconf)); + if (!inst->fconf) { + ha_alert("config: %s '%s' : out of memory.\n", + proxy_type_str(proxy), proxy->id); + return ERR_ALERT | ERR_FATAL; + } + inst->fconf->name = inst->class->name; + + if (inst->class->parse(inst->conf.argv, proxy, inst, &err) < 0) { + ha_alert("config: %s '%s' : error in instance of filter class '%s' from %s:%d : %s.\n", + proxy_type_str(proxy), proxy->id, inst->class->name, + inst->conf.file, inst->conf.line, + err && *err ? err : "unknown error"); + return ERR_ALERT | ERR_FATAL; + } + if (!inst->fconf->ops) { + ha_alert("config: %s '%s' : filter class '%s' defined no callbacks for the instance from %s:%d.\n", + proxy_type_str(proxy), proxy->id, inst->class->name, + inst->conf.file, inst->conf.line); + return ERR_ALERT | ERR_FATAL; + } + return ERR_NONE; +} + +/* Post-parses the instances of the class reference . Only one + * instance is allowed for classes without FLT_CLS_FL_MULTI. Returns a + * combination of ERR_* flags, ERR_NONE on success. + */ +static int flt_precheck_class_instances(struct proxy *proxy, struct filter_class_ref *ref, unsigned int side) +{ + struct filter_instance *inst; + int err_code = ERR_NONE; + int count = 0; + + if (side == FLT_SIDE_REQ) { + list_for_each_entry(inst, &ref->instances, req.list) + count++; + } + else { + list_for_each_entry(inst, &ref->instances, res.list) + count++; + } + + if (count > 1 && !(ref->class->flags & FLT_CLS_FL_MULTI)) { + if (side == FLT_SIDE_REQ) { + list_for_each_entry(inst, &ref->instances, req.list) { + ha_alert("config: %s '%s' : several instances of filter class '%s', but only one is allowed (see %s:%d).\n", + proxy_type_str(proxy), proxy->id, ref->class->name, + inst->conf.file, inst->conf.line); + } + } + else { + list_for_each_entry(inst, &ref->instances, res.list) { + ha_alert("config: %s '%s' : several instances of filter class '%s', but only one is allowed (see %s:%d).\n", + proxy_type_str(proxy), proxy->id, ref->class->name, + inst->conf.file, inst->conf.line); + } + } + return ERR_ALERT | ERR_FATAL; + } + + if (side == FLT_SIDE_REQ) { + list_for_each_entry(inst, &ref->instances, req.list) + err_code |= flt_precheck_instance(proxy, inst); + } + else { + list_for_each_entry(inst, &ref->instances, res.list) + err_code |= flt_precheck_instance(proxy, inst); + } + return err_code; +} + +static int flt_precheck_instances(struct proxy *proxy) +{ + struct filter_class_ref *ref; + int err_code = ERR_NONE; + + /* iterate over all the class references of the proxy: the request side + * first, then the references of the classes with no request side from + * the response side + */ + list_for_each_entry(ref, &proxy->conf.filter_classes_req, list) + err_code |= flt_precheck_class_instances(proxy, ref, FLT_SIDE_REQ); + list_for_each_entry(ref, &proxy->conf.filter_classes_res, list) { + if (LIST_INLIST(&ref->class->req.list)) + continue; /* already handled from the request side */ + err_code |= flt_precheck_class_instances(proxy, ref, FLT_SIDE_RES); + } + return err_code; +} + + +/* Calls flt_precheck_instances() for all proxies, see above */ +static int flt_precheck_instances_all() +{ + struct proxy *px; + int err_code = ERR_NONE; + + list_for_each_entry(px, &main_proxies, el) { + if (px->flags & (PR_FL_DISABLED|PR_FL_STOPPED)) + continue; + + err_code |= flt_precheck_instances(px); + if (err_code & (ERR_ABORT|ERR_FATAL)) { + ha_alert("Failed to parse the filter instances of proxy '%s'.\n", + px->id); + return err_code; + } + /* produce the flat per-side lists of the proxy and release the + * configuration structures + */ + flt_flatten_instances(px); + } + return 0; +} + /* Checks that all registered filter classes are placed on at least one * side. The classes are placed with filter_place_class() (see * filter_init_classes() for the internal classes), so a class without @@ -2009,7 +2480,9 @@ static void filter_init_classes(void) +REGISTER_PRE_CHECK(flt_precheck_instances_all); REGISTER_PRE_CHECK(flt_precheck_classes); +REGISTER_POST_PROXY_CHECK(flt_check_instances); REGISTER_POST_CHECK(flt_init_all); REGISTER_PER_THREAD_INIT(flt_init_all_per_thread); REGISTER_PER_THREAD_DEINIT(flt_deinit_all_per_thread);