From: Christopher Faulet Date: Wed, 30 Sep 2026 16:10:21 +0000 (+0200) Subject: MINOR: filters: Introduce filter classes and per-side class ordering X-Git-Tag: v3.5-dev9~93 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=65613f07d62423564e08ce5be1ba70c9a9fc1723;p=haproxy.git MINOR: filters: Introduce filter classes and per-side class ordering Introduce a class registry distinct from the legacy filter keyword registry. A class has a unique name, some flags, a instance parser and independent request/response placement relative to other classes. Export the built-in class names from their implementations and establish their default order centrally. Provide registration and placement helpers for additional classes, and reject classes placed on neither side. Leave built-in instance parsers unset until the parser adapters are added. This is configuration infrastructure only; stream evaluation still uses the legacy filters. This WIP registry also unconditionally references the Lua class: support for builds without Lua remains to be completed. --- diff --git a/include/haproxy/filters-t.h b/include/haproxy/filters-t.h index ff1d82be3..6716fd0f6 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_instance; /* Descriptor for a "filter" keyword. The ->parse() function returns 0 in case * of success, or a combination of ERR_* flags if an error is encountered. The @@ -257,6 +258,41 @@ struct chn_flt { unsigned long long offset; }; +/**************************************************************/ + +#define FLT_SIDE_REQ 0x01 +#define FLT_SIDE_RES 0x02 + +enum flt_pos { + FLT_POS_BEFORE, + FLT_POS_AFTER, +}; + +/* filter class flags */ +#define FLT_CLS_FL_MULTI 0x01 /* Set if several class instances are allowed per proxy */ + +struct filter_class { + const char *name; /* The filter class name, must be unique */ + unsigned int flags; /* FLT_CLS_FL_* */ + + /* Callback to parse the arguments of a filter instance and produce + * the filter configuration (inst->fconf). + */ + int (*parse)(char **args, struct proxy *px, struct filter_instance *inst, char **err); + struct { + struct list before; /* classes moved before current class for request */ + struct list after; /* classes moved after current class for request */ + struct list list; /* classes registered on request analysis */ + } req; + struct { + struct list before; /* classes moved before current class on response */ + struct list after; /* classes moved after current class on response */ + struct list list; /* classes registered on response analysis */ + } res; + + struct list list; /* Link in global list of filter classes */ +}; + #endif /* _HAPROXY_FILTERS_T_H */ /* diff --git a/include/haproxy/filters.h b/include/haproxy/filters.h index 636cb85db..6e133abc1 100644 --- a/include/haproxy/filters.h +++ b/include/haproxy/filters.h @@ -32,10 +32,40 @@ extern const char *http_comp_req_flt_id; extern const char *http_comp_res_flt_id; extern const char *decomp_req_flt_id; extern const char *decomp_res_flt_id; - extern const char *cache_store_flt_id; + +extern const char *trace_filter_cls_name; +extern const char *cache_store_filter_cls_name; +extern const char *http_comp_req_filter_cls_name; +extern const char *http_comp_res_filter_cls_name; +extern const char *decomp_req_filter_cls_name; +extern const char *decomp_res_filter_cls_name; +extern const char *bwlim_in_filter_cls_name; +extern const char *bwlim_out_filter_cls_name; + +extern struct filter_class flt_trace_cls; +extern struct filter_class flt_cache_store_cls; +extern struct filter_class flt_http_comp_req_cls; +extern struct filter_class flt_http_comp_res_cls; +extern struct filter_class flt_decomp_req_cls; +extern struct filter_class flt_decomp_res_cls; +extern struct filter_class flt_bwlim_in_cls; +extern struct filter_class flt_bwlim_out_cls; + +#if defined(USE_SPOE) extern const char *spoe_filter_id; +extern const char *spoe_filter_cls_name; +extern struct filter_class flt_spoe_cls; +#endif +#if defined(USE_LUA) +extern const char *hlua_filter_cls_name; +extern struct filter_class flt_lua_cls; +#endif +#if defined(USE_FCGI) extern const char *fcgi_flt_id; +extern const char *fcgi_filter_cls_name; +extern struct filter_class flt_fcgi_cls; +#endif #define FLT_ID(flt) (flt)->config->id #define FLT_CONF(flt) (flt)->config->conf @@ -234,4 +264,19 @@ flt_update_offsets(struct filter *filter, struct channel *chn, int len) } } + +/**************************************************************/ + +struct filter_class *filter_find_class(const char *name); + +int filter_register_class(struct filter_class *cls, const char *name, + int (*parse)(char **args, struct proxy *px, + struct filter_instance *inst, char **err)); +int filter_place_class(struct filter_class *cls, unsigned int side, + const char *ref_name, enum flt_pos pos); +int filter_register_class_full(struct filter_class *cls, const char *name, + int (*parse)(char **args, struct proxy *px, + struct filter_instance *inst, char **err), + const char *ref_name, enum flt_pos pos); + #endif /* _HAPROXY_FILTERS_H */ diff --git a/src/cache.c b/src/cache.c index 1235ffbd8..ed8729dcd 100644 --- a/src/cache.c +++ b/src/cache.c @@ -56,6 +56,7 @@ static uint64_t cache_hash_seed = 0; const char *cache_store_flt_id = "cache store filter"; +const char *cache_store_filter_cls_name = "cache-store"; extern struct applet http_cache_applet; diff --git a/src/fcgi-app.c b/src/fcgi-app.c index 71c579ddb..caecc129e 100644 --- a/src/fcgi-app.c +++ b/src/fcgi-app.c @@ -34,6 +34,7 @@ static struct fcgi_app *fcgi_apps = NULL; struct flt_ops fcgi_flt_ops; const char *fcgi_flt_id = "FCGI filter"; +const char *fcgi_filter_cls_name = "fcgi"; /* The fcgi-app filter was implicitly declared (ie without the filter keyword, * via use-fcgi-app). diff --git a/src/filters.c b/src/filters.c index d985de3fa..f85156f22 100644 --- a/src/filters.c +++ b/src/filters.c @@ -30,6 +30,35 @@ #define TRACE_SOURCE &trace_strm +/* All internal filter classes (should be extended if new filters are added) */ +struct filter_class flt_trace_cls; +struct filter_class flt_cache_store_cls; +struct filter_class flt_http_comp_req_cls; +struct filter_class flt_http_comp_res_cls; +struct filter_class flt_decomp_req_cls; +struct filter_class flt_decomp_res_cls; +#if defined(USE_SPOE) +struct filter_class flt_spoe_cls; +#endif +#if defined(USE_LUA) +struct filter_class flt_lua_cls; +#endif +struct filter_class flt_bwlim_in_cls; +struct filter_class flt_bwlim_out_cls; +#if defined(USE_FCGI) +struct filter_class flt_fcgi_cls; +#endif + +/* Global list of all filter classes */ +static struct list filter_classes = LIST_HEAD_INIT(filter_classes); + +/* Ordered list of filter classes dedicated to the request/response processing */ +static struct list req_filter_classes = LIST_HEAD_INIT(req_filter_classes); +static struct list res_filter_classes = LIST_HEAD_INIT(res_filter_classes); + +/* Used to be sure internal filters classes are initialized before any other ones */ +static int filter_classes_initialized = 0; + /* Pool used to allocate filters */ DECLARE_STATIC_TYPED_POOL(pool_head_filter, "filter", struct filter); @@ -1251,10 +1280,283 @@ static struct cfg_kw_list cfg_kws = {ILH, { INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); + +/* 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 + * placement is useless and most probably comes from a module registration + * bug. + * Returns a combination of ERR_* flags, ERR_NONE on success. + */ +static int flt_precheck_classes() +{ + struct filter_class *cls; + int err_code = ERR_NONE; + + list_for_each_entry(cls, &filter_classes, list) { + if (!LIST_INLIST(&cls->req.list) && !LIST_INLIST(&cls->res.list)) { + ha_alert("filters: class '%s' is placed neither on the request side nor on the response side.\n", + cls->name); + err_code |= ERR_ALERT | ERR_FATAL; + } + } + return err_code; +} + +/* Helper function used to initialized a filter class with the given name, + * flags (FLT_CLS_FL_*) and instance parsing function (may be NULL). + */ +static inline void flt_init_class(struct filter_class *cls, const char *name, unsigned int flags, + int (*parse)(char **args, struct proxy *px, + struct filter_instance *inst, char **err)) +{ + cls->name = name; + cls->flags = flags; + cls->parse = parse; + LIST_INIT(&cls->req.before); + LIST_INIT(&cls->req.after); + LIST_INIT(&cls->req.list); + + LIST_INIT(&cls->res.before); + LIST_INIT(&cls->res.after); + LIST_INIT(&cls->res.list); + + LIST_INIT(&cls->list); +} + +/* Finds a filter class by name in the global list, NULL if unknown. */ +struct filter_class *filter_find_class(const char *name) +{ + struct filter_class *cls; + + list_for_each_entry(cls, &filter_classes, list) { + if (strcmp(cls->name, name) == 0) + return cls; + } + return NULL; +} + +/* 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 + * added to the global "filter_classes" list only — it is not usable on any side + * until placed with filter_place_class(). Must be called after + * filter_init_classes(). + * Returns 0 on success, -1 on error (too early call, duplicate name). + */ +int filter_register_class(struct filter_class *cls, const char *name, + int (*parse)(char **args, struct proxy *px, + struct filter_instance *inst, char **err)) +{ + const char *err; + int ret = -1; + + if (!filter_classes_initialized) { + ha_alert("filters: class '%s' registered before internal classes were initialized\n", name); + goto out; + } + if (filter_find_class(name)) { + ha_alert("filters: duplicate filter class name '%s'\n", name); + goto out; + } + err = invalid_prefix_char(name); + if (err) { + ha_alert("filters: character '%c' is not permitted in filter class name.\n", *err); + goto out; + } + + flt_init_class(cls, name, 0, parse); + LIST_APPEND(&filter_classes, &cls->list); + ret = 0; + out: + return ret; +} + +/* Places on (FLT_SIDE_REQ or FLT_SIDE_RES), before or after the + * reference class . The class is appended in the reference class' + * req/res .before or .after constraint list; the final evaluation order is + * resolved later. The global ordered lists are never touched here. Call it + * once per side; the positions may differ per side. + * Returns 0 on success, -1 on error (unknown ref, ref not present on that side, + * class already placed on that side). + */ +int filter_place_class(struct filter_class *cls, unsigned int side, + const char *ref_name, enum flt_pos pos) +{ + struct filter_class *ref; + int ret = -1; + + if (!filter_classes_initialized) { + ha_alert("filters: class '%s' placed before internal classes were initialized\n", cls->name); + goto out; + } + ref = filter_find_class(ref_name); + if (!ref) { + ha_alert("filters: unknown reference class '%s' for class '%s'\n", ref_name, cls->name); + goto out; + } + + if (side == FLT_SIDE_REQ) { + if (LIST_INLIST(&cls->req.list)) { + ha_alert("filters: class '%s' already placed on request side\n", cls->name); + goto out; + } + if (!LIST_INLIST(&ref->req.list)) { + ha_alert("filters: reference class '%s' has no request side\n", ref_name); + goto out; + } + LIST_APPEND((pos == FLT_POS_BEFORE) ? &ref->req.before : &ref->req.after, + &cls->req.list); + } + else { + if (LIST_INLIST(&cls->res.list)) { + ha_alert("filters: class '%s' already placed on response side\n", cls->name); + goto out; + } + if (!LIST_INLIST(&ref->res.list)) { + ha_alert("filters: reference class '%s' has no response side\n", ref_name); + goto out; + } + LIST_APPEND((pos == FLT_POS_BEFORE) ? &ref->res.before : &ref->res.after, + &cls->res.list); + } + ret = 0; + out: + return ret; +} + +/* Registers a new filter class named and places it on both sides, + * before or after the reference class . Convenience function for + * the common symmetric case, equivalent to filter_register_class() followed + * by filter_place_class() on FLT_SIDE_REQ and FLT_SIDE_RES. + * Returns 0 on success, -1 on error. */ +int filter_register_class_full(struct filter_class *cls, const char *name, + int (*parse)(char **args, struct proxy *px, + struct filter_instance *inst, char **err), + const char *ref_name, enum flt_pos pos) +{ + if (filter_register_class(cls, name, parse) < 0) + goto err; + if (filter_place_class(cls, FLT_SIDE_REQ, ref_name, pos) < 0) + goto cleanup_on_err; + if (filter_place_class(cls, FLT_SIDE_RES, ref_name, pos) < 0) + goto cleanup_on_err; + + return 0; + + cleanup_on_err: + LIST_DELETE(&cls->list); + LIST_INIT(&cls->list); + /* fallthrough */ + err: + return -1; +} + +/* Init function responsible to initialize all intenral filter classes and to + * insert them is global list above. The default evaluation order of all + * internal filters is defined in this function. + * + * Exemple to register a new filter class: + * + * static struct filter_class flt_myfilter_cls; + * + * static int myfilter_register_class(void) + * { + * if (filter_register_class(&flt_myfilter_cls, "myfilter", myfilter_parse_def) < 0) + * return -1; + * + * // before spoe on request, after lua on response + * if (filter_place_class(&flt_myfilter_cls, FLT_SIDE_REQ, "spoe", FLT_POS_BEFORE) < 0) + * return -1; + * if (filter_place_class(&flt_myfilter_cls, FLT_SIDE_RES, "lua", FLT_POS_AFTER) < 0) + * return -1; + * return 0; + * } + * INITCALL1(STG_INIT, myfilter_register_class); + */ +static void filter_init_classes(void) +{ + /* TODO: the filter names must come from the filters (the filter id most probably) */ + flt_init_class(&flt_trace_cls, trace_filter_cls_name, FLT_CLS_FL_MULTI, NULL); + flt_init_class(&flt_cache_store_cls, cache_store_filter_cls_name, FLT_CLS_FL_MULTI, NULL); + flt_init_class(&flt_http_comp_req_cls, http_comp_req_filter_cls_name, 0, NULL); + flt_init_class(&flt_http_comp_res_cls, http_comp_res_filter_cls_name, 0, NULL); + flt_init_class(&flt_decomp_req_cls, decomp_req_filter_cls_name, 0, NULL); + flt_init_class(&flt_decomp_res_cls, decomp_res_filter_cls_name, 0, NULL); +#if defined(USE_SPOE) + flt_init_class(&flt_spoe_cls, spoe_filter_cls_name, FLT_CLS_FL_MULTI, NULL); +#endif +#if defined(USE_LUA) + flt_init_class(&flt_lua_cls, hlua_filter_cls_name, FLT_CLS_FL_MULTI, NULL); +#endif + flt_init_class(&flt_bwlim_in_cls, bwlim_in_filter_cls_name, FLT_CLS_FL_MULTI, NULL); + flt_init_class(&flt_bwlim_out_cls, bwlim_out_filter_cls_name, FLT_CLS_FL_MULTI, NULL); +#if defined(USE_FCGI) + flt_init_class(&flt_fcgi_cls, fcgi_filter_cls_name, 0, NULL); +#endif + + LIST_APPEND(&filter_classes, &flt_trace_cls.list); + LIST_APPEND(&filter_classes, &flt_cache_store_cls.list); + LIST_APPEND(&filter_classes, &flt_decomp_req_cls.list); + LIST_APPEND(&filter_classes, &flt_decomp_res_cls.list); +#if defined(USE_SPOE) + LIST_APPEND(&filter_classes, &flt_spoe_cls.list); +#endif +#if defined(USE_LUA) + LIST_APPEND(&filter_classes, &flt_lua_cls.list); +#endif + LIST_APPEND(&filter_classes, &flt_http_comp_req_cls.list); + LIST_APPEND(&filter_classes, &flt_http_comp_res_cls.list); + LIST_APPEND(&filter_classes, &flt_bwlim_in_cls.list); + LIST_APPEND(&filter_classes, &flt_bwlim_out_cls.list); +#if defined(USE_FCGI) + LIST_APPEND(&filter_classes, &flt_fcgi_cls.list); +#endif + + LIST_APPEND(&req_filter_classes, &flt_trace_cls.req.list); + LIST_APPEND(&req_filter_classes, &flt_decomp_req_cls.req.list); +#if defined(USE_LUA) + LIST_APPEND(&req_filter_classes, &flt_lua_cls.req.list); +#endif +#if defined(USE_SPOE) + LIST_APPEND(&req_filter_classes, &flt_spoe_cls.req.list); +#endif + LIST_APPEND(&req_filter_classes, &flt_http_comp_res_cls.req.list); + LIST_APPEND(&req_filter_classes, &flt_decomp_res_cls.req.list); + LIST_APPEND(&req_filter_classes, &flt_http_comp_req_cls.req.list); + LIST_APPEND(&req_filter_classes, &flt_bwlim_in_cls.req.list); +#if defined(USE_FCGI) + LIST_APPEND(&req_filter_classes, &flt_fcgi_cls.req.list); +#endif + +#if defined(USE_FCGI) + LIST_APPEND(&res_filter_classes, &flt_fcgi_cls.res.list); +#endif + LIST_APPEND(&res_filter_classes, &flt_trace_cls.res.list); + LIST_APPEND(&res_filter_classes, &flt_decomp_res_cls.res.list); + LIST_APPEND(&res_filter_classes, &flt_cache_store_cls.res.list); +#if defined(USE_SPOE) + LIST_APPEND(&res_filter_classes, &flt_spoe_cls.res.list); +#endif +#if defined(USE_LUA) + LIST_APPEND(&res_filter_classes, &flt_lua_cls.res.list); +#endif + LIST_APPEND(&res_filter_classes, &flt_http_comp_res_cls.res.list); + LIST_APPEND(&res_filter_classes, &flt_decomp_req_cls.res.list); + LIST_APPEND(&res_filter_classes, &flt_bwlim_out_cls.res.list); + + filter_classes_initialized = 1; +} + + + +REGISTER_PRE_CHECK(flt_precheck_classes); REGISTER_POST_CHECK(flt_init_all); REGISTER_PER_THREAD_INIT(flt_init_all_per_thread); REGISTER_PER_THREAD_DEINIT(flt_deinit_all_per_thread); +INITCALL0(STG_REGISTER, filter_init_classes); + /* * Local variables: * c-indent-level: 8 diff --git a/src/flt_bwlim.c b/src/flt_bwlim.c index f94706423..6c61c43df 100644 --- a/src/flt_bwlim.c +++ b/src/flt_bwlim.c @@ -28,6 +28,8 @@ const char *bwlim_in_flt_id = "input bandwidth limitation filter"; const char *bwlim_out_flt_id = "output bandwidth limitation filter"; +const char *bwlim_in_filter_cls_name = "bwlim-in"; +const char *bwlim_out_filter_cls_name = "bwlim-out"; struct flt_ops bwlim_ops; diff --git a/src/flt_decomp.c b/src/flt_decomp.c index 16b4c1a7e..72ee1c836 100644 --- a/src/flt_decomp.c +++ b/src/flt_decomp.c @@ -38,6 +38,8 @@ const char *decomp_req_flt_id = "decomp-req filter"; const char *decomp_res_flt_id = "decomp-res filter"; +const char *decomp_req_filter_cls_name = "decomp-req"; +const char *decomp_res_filter_cls_name = "decomp-res"; struct flt_ops decomp_req_ops; struct flt_ops decomp_res_ops; diff --git a/src/flt_http_comp.c b/src/flt_http_comp.c index 0c3650e97..e28193222 100644 --- a/src/flt_http_comp.c +++ b/src/flt_http_comp.c @@ -29,6 +29,8 @@ const char *http_comp_req_flt_id = "comp-req filter"; const char *http_comp_res_flt_id = "comp-res filter"; +const char *http_comp_req_filter_cls_name = "http-comp-req"; +const char *http_comp_res_filter_cls_name = "http-comp-res"; struct flt_ops comp_req_ops; struct flt_ops comp_res_ops; diff --git a/src/flt_spoe.c b/src/flt_spoe.c index 64a330089..aa5287d50 100644 --- a/src/flt_spoe.c +++ b/src/flt_spoe.c @@ -221,6 +221,7 @@ struct spoe_config { /* SPOE filter id. Used to identify SPOE filters */ const char *spoe_filter_id = "SPOE filter"; +const char *spoe_filter_cls_name = "spoe"; /* The name of the SPOE engine, used during the parsing */ char *curengine = NULL; diff --git a/src/flt_trace.c b/src/flt_trace.c index cd9e5dbc3..3284f37d1 100644 --- a/src/flt_trace.c +++ b/src/flt_trace.c @@ -26,6 +26,7 @@ #include const char *trace_flt_id = "trace filter"; +const char *trace_filter_cls_name = "trace"; struct flt_ops trace_ops; diff --git a/src/hlua.c b/src/hlua.c index 58b6bbe8e..3c54791b2 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -416,6 +416,8 @@ static lua_State *hlua_states[MAX_THREADS + 1]; #define HLUA_FLT_CTX_FL_PAYLOAD 0x00000001 +const char *hlua_filter_cls_name = "lua"; + struct hlua_reg_filter { char *name; int flt_ref[MAX_THREADS + 1];