From: Christopher Faulet Date: Mon, 26 Oct 2026 10:16:59 +0000 (+0100) Subject: MINOR: filters: Deduce the default filter API from the implied filters X-Git-Tag: v3.5-dev10~39 X-Git-Url: https://git.kaiwu.me/$%7BISSUES%7D/static/gitweb.js?a=commitdiff_plain;h=0e31a3239ed95048f2f4331cf5d1d41b1972dd7e;p=haproxy.git MINOR: filters: Deduce the default filter API from the implied filters Until now, a proxy with no explicit filter declaration (neither "filter" nor "filter-*" directives) always used the new filter API, except when the legacy API was globally forced with "filter-api downgrade-to-legacy". But a third-party filter relying on an implicit declaration only, and not registering a filter class, was silently ignored in the new API: such a filter declares a legacy filter configuration, without any class, hence without any instance. Now the API used by a proxy without explicit filter declaration is deduced from what its other directives imply. A proxy with filter configurations and no filter instance uses the legacy API: the implied filters declared no class, so they are invisible to the new API. A proxy with filter instances uses the new API, where the class-less filters it may also imply are ignored. The explicit declarations and the "filter-api downgrade-to-legacy" option keep precedence, as before. All the filters supported in-tree register their classes and are instantiated in the new API, so they are unaffected: a proxy implying only in-tree filters always has instances, and keeps using the new API. The change only makes the class-less third-party filters work again without the global downgrade, and it is the first step towards making the new API the only one. The documentation is updated accordingly, in the filters internals guide and in the configuration manual, for the "filter-api" option and the filter mode selection. --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 25bae6938..88c50bb47 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -2819,13 +2819,18 @@ filter-api default | downgrade-to-legacy used ("filter", "filter-config", "filter-enable", "filter-disable" or "filter-sequence"). - This option is unset by default. So proxies without explicit filter - directives rely on the filter instance api. By setting it to - "downgrade-to-legacy", it is possible to force proxies to use the legacy - filter API by default. It could be useful the time to convert outdated - filters still relying on the legacy API only. Indeed, such filters will be - silently ignored except if the "filter" directive is used to declare them. - This option can be set to "default" to restore the default behavior. + This option is unset by default. The API used by a proxy without explicit + filter directives is deduced from the filters implied by its other + directives: a proxy implying only filters known as legacy filter + configurations, without any filter class, uses the legacy API, while a + proxy implying at least one filter class uses the filter instance API, and + the class-less filters it may also imply are ignored. By setting this option + to "downgrade-to-legacy", it is possible to force the proxies without + explicit filter directives to use the legacy filter API, whatever they + imply. It could be useful the time to convert outdated filters still relying + on the legacy API only. Indeed, such filters will be silently ignored in + the filter instance API, except if the "filter" directive is used to declare + them. This option can be set to "default" to restore the default behavior. Note that it should be a temporary workaround. There is no warranty that all filters will remain compatible with the legacy API. In that case, they will @@ -31788,11 +31793,16 @@ are inherited from a defaults section. Conversely, a legacy "filter" directive is rejected in a proxy inheriting filter instances from its defaults section. Filters implicitly declared by other directives (for instance by the "compression" options, the "cache-store" rules or "use-fcgi-app") do not -select a mode: a proxy using only such filters works in the new mode. +select a mode: a proxy using only such filters works in the new mode, unless +the implied filters are only known as legacy filter configurations, without +any filter class: a proxy implying only such filters works in the legacy +mode, and a proxy implying filters of both kinds uses the new mode, where the +class-less ones are ignored. All the filters documented in this section +declare their classes, so they are always visible to the new mode. The "filter-api" global option changes this last rule: when it is set to "downgrade-to-legacy", the proxies using only implicitly declared filters work -in the legacy mode, so the filters not supporting the new mode keep -working. Explicit filter declarations always win, in both modes. +in the legacy mode, whatever they imply, so the filters not supporting the +new mode keep working. Explicit filter declarations always win, in both modes. All the filters documented below support the new mode. The name to use with "filter-config" may differ from the legacy filter name; it is given in diff --git a/doc/internals/api/filters.txt b/doc/internals/api/filters.txt index 7c384f8e6..2f94a9d5a 100644 --- a/doc/internals/api/filters.txt +++ b/doc/internals/api/filters.txt @@ -83,9 +83,13 @@ declaration is an instance, possibly with an id. A proxy uses one mode or the other, never both: as soon as a legacy "filter" directive is used, the proxy works in legacy mode, otherwise the new mode is used. Implicitly declared filters do not select a mode, so a proxy using only implicit filters works in -the new mode. The "filter-api" global option changes this last rule: when it -is set to "downgrade-to-legacy", a proxy using only implicitly declared filters -works in the legacy mode. It is a fallback for the filters not supporting the +the new mode, unless the implicitly declared filters are only known as legacy +filter configurations: a filter not supporting the new mode declares no +class, hence no instance, and a proxy implying such filters keeps working in +the legacy mode. A proxy implying filters of both kinds uses the new mode, +where the class-less ones are ignored. The "filter-api" global option forces +the legacy mode for every proxy using only implicitly declared filters, +whatever they imply. It is a fallback for the filters not supporting the new mode, and the explicit declarations always win. A filter class defines the filter name, its placement in the request and @@ -1655,9 +1659,12 @@ supporting both modes must not scan 'px->filter_configs' directly. A filter written before HAProxy 3.5 only supports the legacy mode. It keeps working as-is, nothing is required to use it with the "filter" directive. -But it is invisible to the new mode until its class is declared. If it -relies on an implicit declaration only, the "filter-api downgrade-to-legacy" -global option keeps it working in the meantime, see the FAQ (section 5.7). +But it is invisible to the new mode until its class is declared. A proxy +implying only such filters keeps working in the legacy mode, since HAProxy +3.5 deduces it from the implied declarations, but a proxy implying filters +of both kinds ignores the class-less ones: the "filter-api +downgrade-to-legacy" global option keeps them working in the meantime, see +the FAQ (section 5.7). This section lists what changed for a filter developer, then explains how to convert an existing filter, first with the minimal changes to support both modes, then to take full advantage of the new mode. @@ -2025,7 +2032,13 @@ enabled by another directive, or is referenced by other directives. filter class is invisible to the new mode: the declarations implying it are silently ignored, because there is no filter instance to instantiate. All the filters supported in-tree are available in both modes, so this - only concerns third-party filters. + only concerns third-party filters. A proxy implying only such filters + works in the legacy mode by default, since HAProxy 3.5 deduces the mode + from the implied declarations, so they keep working as-is. The problem + only arises for a proxy implying filters of both kinds: the class-less + ones are ignored. All the filters implied by such a proxy must then + support the new mode, or the legacy ones must be declared explicitly + with the "filter" directive. In this case, the "filter-api downgrade-to-legacy" global option restores the behavior of HAProxy 3.4: the proxies without explicit filter diff --git a/src/filters.c b/src/filters.c index f07039252..9987ed5f3 100644 --- a/src/filters.c +++ b/src/filters.c @@ -3175,19 +3175,67 @@ static int flt_precheck_instances(struct proxy *proxy) } -/* Applies the default filter mode on the proxy : when the legacy filter - * mode was requested globally, a proxy with no explicit filter declaration - * (neither "filter" nor "filter-*" directives) uses the legacy mode instead - * of the new one, so the implicit declarations of the filters not supporting - * the new mode keep working. A proxy with explicit declarations keeps the - * mode it selected: the legacy mode for the "filter" directives, the new - * one for the "filter-*" directives. +/* Returns true if the proxy has at least one filter instance, explicit + * or implicit, in its configuration state. Must be called before the instances + * are flattened in px->filter_instances, so they are walked through their + * class references. + */ +static int flt_has_conf_instances(const struct proxy *px) +{ + struct filter_class_ref *ref; + + list_for_each_entry(ref, &px->conf.filter_refs, list) { + if (!LIST_ISEMPTY(&ref->instances)) + return 1; + } + return 0; +} + +/* Applies the default filter mode on the proxy , when it used no explicit + * filter declaration (neither "filter" nor "filter-*" directives): the mode + * is deduced from the filter configurations and the filter instances implied + * by the other directives. A proxy with only filter configurations, and no + * instance, uses the legacy mode: an implied filter not supporting the new + * mode declares no class, hence no instance. A proxy with filter instances + * uses the new mode: the implied filters all declared their classes. A proxy + * with both may mix implied filters of the two kinds, and uses the new mode, + * so the legacy-only ones are ignored, as any class-less filter in the new + * mode. A proxy with explicit declarations keeps the mode it selected: the + * legacy mode for the "filter" directives, the new one for the "filter-*" + * directives. When the legacy filter mode was requested globally, a proxy + * with no explicit declaration uses the legacy mode instead of the new one, + * whatever it deduced, so the implicit declarations of the filters not + * supporting the new mode keep working. */ static void flt_apply_default_mode(struct proxy *px) { - if (filter_api_downgraded == 1 && - !flt_use_legacy_filter(px) && !flt_has_explicit_config(px)) + if (flt_use_legacy_filter(px) || flt_has_explicit_config(px)) + return; + + if (filter_api_downgraded == 1) { + px->flags |= PR_FL_FILTER_LEGACY; + return; + } + + /* Filter configurations with no instance: the implied filters declared + * no class, so they are only usable in the legacy mode. + */ + if (!LIST_ISEMPTY(&px->filter_configs) && !flt_has_conf_instances(px)) { + struct flt_conf *fconf; + int first = 1; + + chunk_reset(&trash); + chunk_printf(&trash, "config: %s '%s' : All filters used are relying only on legacy API " + "(set 'filter-api downgrade-to-legacy' in a global section to remove this warning): \n\t", + proxy_type_str(px), px->id); + list_for_each_entry(fconf, &px->filter_configs, list) { + chunk_appendf(&trash, "%s'%s'", (first ? " " : ", "), fconf->id); + first = 0; + } + ha_warning("%.*s\n", (int)trash.data, trash.area); + px->flags |= PR_FL_FILTER_LEGACY; + } } /* Calls flt_precheck_instances() for all proxies, see above */