From: Christopher Faulet Date: Wed, 30 Sep 2026 16:10:21 +0000 (+0200) Subject: MINOR: filters: Add per-proxy filter instance storage and lookup X-Git-Tag: v3.5-dev9~92 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=0fe608c13f4e9f8029b1eb7eecbf98acff65959f;p=haproxy.git MINOR: filters: Add per-proxy filter instance storage and lookup Prepare a instance model separate from the instantiated legacy filter configurations. Keep raw arguments and source locations, and anchor each instance in the class references for the sides on which its class exists. Per-side flat lists will hold the finalized order later. Create class references when setting up non-internal proxies, following the global class order. Add instance traversal and lookup helpers, plus cleanup for both the class-reference trees and flat lists on proxy destruction. Prepare per-side links in flt_conf for the later runtime integration. No configuration directive creates instances yet, and stream attachment remains unchanged. --- diff --git a/include/haproxy/filters-t.h b/include/haproxy/filters-t.h index 6716fd0f6..20c2ac4fe 100644 --- a/include/haproxy/filters-t.h +++ b/include/haproxy/filters-t.h @@ -43,6 +43,7 @@ struct stream; struct channel; struct flt_conf; struct filter; +struct filter_class_ref; struct filter_instance; /* Descriptor for a "filter" keyword. The ->parse() function returns 0 in case @@ -208,12 +209,14 @@ struct flt_ops { * accessible from a filter when instantiated in a stream */ struct flt_conf { - const char *name; /* The filter name (same name used to select the filter from config) */ - const char *id; /* The filter id */ - struct flt_ops *ops; /* The filter callbacks */ - void *conf; /* The filter configuration */ - struct list list; /* Next filter for the same proxy */ - unsigned int flags; /* FLT_CFG_FL_* */ + const char *name; /* The filter name (same name used to select the filter from config) */ + const char *id; /* The filter id */ + struct flt_ops *ops; /* The filter callbacks */ + void *conf; /* The filter configuration */ + unsigned int flags; /* FLT_CFG_FL_* */ + struct list list; /* Next filter for the same proxy */ + struct list by_req; /* Link in the list of filter to eval on the requet path */ + struct list by_res; /* Link in the list of filter to eval on the response path */ }; /* @@ -293,6 +296,50 @@ struct filter_class { struct list list; /* Link in global list of filter classes */ }; +/* Per-proxy and per-side reference to a filter class. It anchors the + * instances of the class evaluated on this side for one proxy. The + * reference is inserted in the per-side list of the proxy, following the + * global class order of the side, so that the instances of a proxy are, + * by default, evaluated in the global class order and not in the parsing + * order. + */ +struct filter_class_ref { + struct filter_class *class; /* The referenced filter class */ + struct list reordered_before; /* instances moved before all instances of this class, on this side */ + struct list reordered_after; /* instances moved after all instances of this class, on this side */ + struct list instances; /* instances of this class evaluated on this side (parsing order) */ + struct list list; /* Link in one of the proxy's class refs list */ +}; + +/* filter instance flags */ +#define FLT_INST_F_IMPLICIT 0x01 /* The instance comes from an implicit declaration (e.g. use-fcgi-app) */ +#define FLT_INST_F_INHERITED 0x02 /* The instance was inherited from a defaults section */ + +struct filter_instance { + const char *id; /* The filter instance id, uniq for a class and a proxy */ + unsigned int enabled; /* != 0 if enabled */ + unsigned int flags; /* FLT_INST_F_* */ + struct filter_class *class; /* filter class for this instance */ + struct proxy *px; /* proxy owning this instance */ + struct flt_conf *fconf; /* filter configuration */ + struct { + char *file; + int line; + int argc; + char **argv; + } conf; /* The raw configuration used during parsing */ + struct { + struct list reordered_before; /* instances moved before this instance on request */ + struct list reordered_after; /* instances moved after this instance on request */ + struct list list; /* Link in the request side list holding this instance */ + } req; + struct { + struct list reordered_before; /* instances moved before this instance on response */ + struct list reordered_after; /* instances moved after this instance on response */ + struct list list; /* Link in the response side list holding this instance */ + } res; +}; + #endif /* _HAPROXY_FILTERS_T_H */ /* diff --git a/include/haproxy/filters.h b/include/haproxy/filters.h index 6e133abc1..6b6b7bb79 100644 --- a/include/haproxy/filters.h +++ b/include/haproxy/filters.h @@ -129,6 +129,9 @@ extern struct filter_class flt_fcgi_cls; void flt_deinit(struct proxy *p); int flt_check(struct proxy *p); +void flt_free_instances(struct proxy *px); +int flt_init_class_refs(struct proxy *px); +struct filter_instance *flt_find_instance(struct proxy *px, struct filter_class *cls, const char *id); int flt_stream_start(struct stream *s); void flt_stream_stop(struct stream *s); diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h index 64a555f69..0200cf81b 100644 --- a/include/haproxy/proxy-t.h +++ b/include/haproxy/proxy-t.h @@ -490,6 +490,8 @@ struct proxy { const char *file_prev; /* file of the previous instance found with the same name, or NULL */ int line_prev; /* line of the previous instance found with the same name, or 0 */ unsigned int def_ref; /* default proxy only refcount */ + struct list filter_classes_req; /* list of the filter class refs with a request side */ + struct list filter_classes_res; /* list of the filter class refs with a response side */ } conf; /* config information */ struct http_ext *http_ext; /* http ext options */ struct ceb_root *used_server_addr; /* list of server addresses in use */ @@ -517,6 +519,8 @@ struct proxy { * name is used */ struct list filter_configs; /* list of the filters that are declared on this proxy */ + struct list filter_req_instances; /* flat list of the filter instances evaluated on the request side */ + struct list filter_res_instances; /* flat list of the filter instances evaluated on the response side */ 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 f85156f22..4adcafbca 100644 --- a/src/filters.c +++ b/src/filters.c @@ -64,6 +64,49 @@ DECLARE_STATIC_TYPED_POOL(pool_head_filter, "filter", struct filter); static int handle_analyzer_result(struct stream *s, struct channel *chn, unsigned int an_bit, int ret); +/* Frees a filter instance and all its content */ +static void flt_free_instance(struct filter_instance *inst) +{ + if (!inst) + return; + if (LIST_INLIST(&inst->req.list)) + LIST_DELETE(&inst->req.list); + if (LIST_INLIST(&inst->res.list)) + LIST_DELETE(&inst->res.list); + if (inst->conf.argv) { + int i; + + for (i = 0; i < inst->conf.argc; i++) + free(inst->conf.argv[i]); + free(inst->conf.argv); + } + free(inst->conf.file); + free((void *)inst->id); + free(inst->fconf); + free(inst); +} + +/* Frees the instances of the list for side , recursively */ +static void flt_free_inst_list(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_free_inst_list(&inst->req.reordered_before, side); + flt_free_inst_list(&inst->req.reordered_after, side); + flt_free_instance(inst); + } + } + else { + list_for_each_entry_safe(inst, back, instances, res.list) { + flt_free_inst_list(&inst->res.reordered_before, side); + flt_free_inst_list(&inst->res.reordered_after, side); + flt_free_instance(inst); + } + } +} + /* * The API below is similar to flt_list_start() and flt_list_next() except that it can be * interrupted and resumed! @@ -437,6 +480,7 @@ flt_deinit(struct proxy *proxy) LIST_DELETE(&fconf->list); free(fconf); } + flt_free_instances(proxy); } /* @@ -1265,6 +1309,173 @@ handle_analyzer_result(struct stream *s, struct channel *chn, return 0; } +/* Iterates over the instances of the list for side , + * recursively. is called for each instance and must return 0 to + * continue, any other value to stop immediately; that value is then returned. + * + * Note: it must only be used on loop-free structures (the sequence loop + * detection runs after the sequences are applied, and aborts the startup + * on a loop). + */ +static int flt_foreach_instance(struct list *instances, unsigned int side, + int (*fct)(struct filter_instance *inst, void *data), void *data) +{ + struct filter_instance *inst; + int ret; + + if (side == FLT_SIDE_REQ) { + list_for_each_entry(inst, instances, req.list) { + ret = fct(inst, data); + if (ret) + return ret; + ret = flt_foreach_instance(&inst->req.reordered_before, side, fct, data); + if (ret) + return ret; + ret = flt_foreach_instance(&inst->req.reordered_after, side, fct, data); + if (ret) + return ret; + } + } + else { + list_for_each_entry(inst, instances, res.list) { + ret = fct(inst, data); + if (ret) + return ret; + ret = flt_foreach_instance(&inst->res.reordered_before, side, fct, data); + if (ret) + return ret; + ret = flt_foreach_instance(&inst->res.reordered_after, side, fct, data); + if (ret) + return ret; + } + } + return 0; +} + +/* Iterates over all the instances of the proxy for side , + * recursively. + */ +static int flt_foreach_instance_side(struct proxy *px, unsigned int side, + int (*fct)(struct filter_instance *inst, void *data), void *data) +{ + struct list *refs = (side == FLT_SIDE_REQ ? &px->conf.filter_classes_req : &px->conf.filter_classes_res); + struct filter_class_ref *ref; + int ret; + + list_for_each_entry(ref, refs, list) { + ret = flt_foreach_instance(&ref->instances, side, fct, data); + if (ret) + return ret; + ret = flt_foreach_instance(&ref->reordered_before, side, fct, data); + if (ret) + return ret; + ret = flt_foreach_instance(&ref->reordered_after, side, fct, data); + if (ret) + return ret; + } + return 0; +} + +/* Context for flt_match_instance_cb() */ +struct flt_match_instance_ctx { + struct filter_class *cls; /* the class to match */ + const char *id; /* the id to match, NULL for any id */ + unsigned int side; /* the side being iterated (FLT_SIDE_REQ or FLT_SIDE_RES) */ + struct filter_instance *found; /* the first matching instance, if any */ + int count; /* the number of matching instances */ +}; + +/* Callback matching the instances of the class ctx->cls with the id + * ctx->id (any id if NULL). The first match is recorded in ctx->found and + * the matches are counted in ctx->count (a instance of a class placed on + * both sides is counted once). With an id, the iteration stops at the + * first match. + */ +static int flt_match_instance_cb(struct filter_instance *inst, void *data) +{ + struct flt_match_instance_ctx *ctx = data; + + if (inst->class != ctx->cls) + return 0; + if (ctx->side == FLT_SIDE_RES && LIST_INLIST(&inst->req.list)) + return 0; /* already matched from the request side */ + if (ctx->id && (!inst->id || strcmp(inst->id, ctx->id) != 0)) + return 0; + if (!ctx->found) + ctx->found = inst; + ctx->count++; + return ctx->id ? 1 : 0; +} + +/* Finds the filter instance of the class with the id in the proxy + * . If is NULL, the first instance of the class is returned. The + * number of matching instances is returned in <*count> if not NULL. + * Returns NULL if not found. + */ +static struct filter_instance *flt_find_instance_count(struct proxy *px, struct filter_class *cls, + const char *id, int *count) +{ + struct flt_match_instance_ctx ctx = { .cls = cls, .id = id, .found = NULL, .count = 0 }; + + /* instances are in the flat per-side lists of the proxy, or in the + * class references if the configuration was not flattened yet (or + * for the defaults sections): the request side first, then the + * response side + */ + ctx.side = FLT_SIDE_REQ; + if (flt_foreach_instance(&px->filter_req_instances, FLT_SIDE_REQ, flt_match_instance_cb, &ctx) && ctx.found) + goto end; + ctx.side = FLT_SIDE_RES; + if (flt_foreach_instance(&px->filter_res_instances, FLT_SIDE_RES, flt_match_instance_cb, &ctx) && ctx.found) + goto end; + ctx.side = FLT_SIDE_REQ; + if (flt_foreach_instance_side(px, FLT_SIDE_REQ, flt_match_instance_cb, &ctx) && ctx.found) + goto end; + ctx.side = FLT_SIDE_RES; + flt_foreach_instance_side(px, FLT_SIDE_RES, flt_match_instance_cb, &ctx); + + end: + if (count) + *count = ctx.count; + return ctx.found; +} + +/* Finds the filter instance of the class with the id in the proxy + * . If is NULL, the first instance of the class is returned. + * Returns NULL if not found. + */ +struct filter_instance *flt_find_instance(struct proxy *px, struct filter_class *cls, const char *id) +{ + return flt_find_instance_count(px, cls, id, NULL); +} + + +/* Frees all filter instances of the proxy , its filter class + * references, its filter-enable entries and its filter sequences. + */ +void flt_free_instances(struct proxy *px) +{ + struct filter_class_ref *ref, *refback; + + flt_free_inst_list(&px->filter_req_instances, FLT_SIDE_REQ); + flt_free_inst_list(&px->filter_res_instances, FLT_SIDE_RES); + list_for_each_entry_safe(ref, refback, &px->conf.filter_classes_req, list) { + flt_free_inst_list(&ref->instances, FLT_SIDE_REQ); + flt_free_inst_list(&ref->reordered_before, FLT_SIDE_REQ); + flt_free_inst_list(&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_free_inst_list(&ref->instances, FLT_SIDE_RES); + flt_free_inst_list(&ref->reordered_before, FLT_SIDE_RES); + flt_free_inst_list(&ref->reordered_after, FLT_SIDE_RES); + LIST_DELETE(&ref->list); + free(ref); + } + +} + /* Note: must not be declared as its list will be overwritten. * Please take care of keeping this list alphabetically sorted, doing so helps @@ -1336,6 +1547,86 @@ struct filter_class *filter_find_class(const char *name) return NULL; } +/* Creates the filter class references for the request side. Returns 0 on + * success, -1 on error. + */ +static int flt_init_req_class_refs(struct proxy *px, struct list *classes) +{ + struct filter_class *cls; + struct filter_class_ref *ref; + + list_for_each_entry(cls, classes, req.list) { + if (flt_init_req_class_refs(px, &cls->req.before) == -1) + goto error; + + ref = calloc(1, sizeof(*ref)); + if (!ref) + goto error; + ref->class = cls; + LIST_INIT(&ref->instances); + LIST_INIT(&ref->reordered_before); + LIST_INIT(&ref->reordered_after); + LIST_APPEND(&px->conf.filter_classes_req, &ref->list); + + if (flt_init_req_class_refs(px, &cls->req.after) == -1) + goto error; + } + + return 0; + error: + return -1; +} + +/* Creates the filter class references for the response side. Returns 0 on + * success, -1 on error. + */ +static int flt_init_res_class_refs(struct proxy *px, struct list *classes) +{ + struct filter_class *cls; + struct filter_class_ref *ref; + + list_for_each_entry(cls, classes, res.list) { + if (flt_init_res_class_refs(px, &cls->res.before) == -1) + goto error; + + ref = calloc(1, sizeof(*ref)); + if (!ref) + goto error; + ref->class = cls; + LIST_INIT(&ref->instances); + LIST_INIT(&ref->reordered_before); + LIST_INIT(&ref->reordered_after); + LIST_APPEND(&px->conf.filter_classes_res, &ref->list); + + if (flt_init_res_class_refs(px, &cls->res.after) == -1) + goto error; + } + + return 0; + error: + return -1; +} + +/* Creates the filter class references of the proxy : one reference per + * registered filter class, linked in the per-side lists of the proxy following + * the global class order of each side. + * It is called during the proxy initialization, so all filter classes must be + * registered first. + * Returns 0 on success, -1 on error. + */ +int flt_init_class_refs(struct proxy *px) +{ + if (flt_init_req_class_refs(px, &req_filter_classes) == -1) + goto error; + if (flt_init_res_class_refs(px, &res_filter_classes) == -1) + goto error; + return 0; + + error: + flt_free_instances(px); + return -1; +} + /* Registers a new filter class, with the parsing function used to * finalize the filter instances of this class (may be NULL if the class does * not support the "filter-config" directive). The class is initialized and diff --git a/src/proxy.c b/src/proxy.c index ee759127a..bb187722b 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -1635,6 +1635,10 @@ void init_new_proxy(struct proxy *p) LIST_INIT(&p->conf.args.list); LIST_INIT(&p->conf.lf_checks); LIST_INIT(&p->filter_configs); + LIST_INIT(&p->filter_req_instances); + LIST_INIT(&p->filter_res_instances); + LIST_INIT(&p->conf.filter_classes_req); + LIST_INIT(&p->conf.filter_classes_res); LIST_INIT(&p->tcpcheck.preset_vars); MT_LIST_INIT(&p->lbprm.lb_free_list); @@ -2945,6 +2949,7 @@ static void defaults_px_free(struct proxy *defproxy) proxy_free_common(defproxy); /* default proxy specific cleanup */ + flt_free_instances(defproxy); if (defproxy->defsrv) srv_free_params(defproxy->defsrv); ha_free(&defproxy->defbe.name); @@ -3207,6 +3212,14 @@ int setup_new_proxy(struct proxy *px, const char *name, unsigned int cap, char * px->cap = cap; px->last_change = ns_to_sec(now_ns); + /* Create the filter class references. Filters are not supported on + * internal proxies, they are skipped for them. + */ + if (!(cap & PR_CAP_INT) && flt_init_class_refs(px) < 0) { + memprintf(errmsg, "out of memory"); + goto fail; + } + /* Internal proxies or with empty name are not stored in the named tree. */ if (name && name[0] != '\0' && !(cap & PR_CAP_INT)) proxy_store_name(px);