                   -----------------------------------------
                          Filters Guide - version 3.5
                          ( Last update: 2026-10-08 )
                   ------------------------------------------
                          Author : Christopher Faulet
              Contact : christopher dot faulet at capflam dot org


ABSTRACT
--------

The filters support is a new feature of HAProxy 1.7. It is a way to extend
HAProxy without touching its core code and, in certain extent, without knowing
its internals. This feature will ease contributions, reducing impact of
changes. Another advantage will be to simplify HAProxy by replacing some parts
by filters. As we will see, and as an example, the HTTP compression is the first
feature moved in a filter.

This document describes how to write a filter and what to keep in mind to do
so. It also talks about the known limits and the pitfalls to avoid.

As said, filters are quite new for now. The API is not freezed and will be
updated/modified/improved/extended as needed.



SUMMARY
-------

  1.    Filters introduction
  2.    How to use filters
  2.1.      Legacy declarations
  2.2.      Filter instances
  3.    How to write a new filter
  3.1.      API Overview
  3.2.      Defining the filter class and its configuration
  3.2.1.        The filter class parse callback
  3.2.2.        The legacy keyword parser
  3.3.      Managing the filter lifecycle
  3.3.1.        Dealing with threads
  3.4.      Handling the streams activity
  3.5.      Analyzing the channels activity
  3.6.      Filtering the data exchanged
  3.7.      Referencing a filter configuration from other directives
  4.    Porting a legacy filter
  4.1.      What changed
  4.2.      Minimal changes
  4.3.      Fully using the filter instance mode
  4.4.      Pitfalls and design considerations
  5.    FAQ



1. FILTERS INTRODUCTION
-----------------------

First of all, to fully understand how filters work and how to create one, it is
best to know, at least from a distance, what is a proxy (frontend/backend), a
stream and a channel in HAProxy and how these entities are linked to each other.
In doc/internals/api/layers.txt is a good overview of the different layers in
HAProxy and in doc/internals/muxes.pdf is described the flow between the
different muxes.

Then, to support filters, many callbacks has been added to HAProxy at different
places, mainly around channel analyzers. Their purpose is to allow filters to
be involved in the data processing, from the stream creation/destruction to
the data forwarding. Depending of what it should do, a filter can implement all
or part of these callbacks. For now, existing callbacks are focused on
streams. But future improvements could enlarge filters scope. For instance, it
could be useful to handle events at the connection level.

In HAProxy configuration file, a filter is declared in a proxy section, except
default. So the configuration corresponding to a filter declaration is attached
to a specific proxy, and will be shared by all its stream filter instances. It
is opaque from the HAProxy point of view, this is the filter responsibility to
manage it.

Since HAProxy 3.5, two declaration modes coexist. The historical one, called
the legacy mode, is based on the "filter" directive and is kept for backward
compatibility. The new mode is based on filter instances declared with
"filter-config". In this mode, a filter is described by a filter class and each
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
new mode, and the explicit declarations always win.

A filter class defines the filter name, its placement in the request and
response evaluation orders, and how its instances are parsed. A filter instance
is the configuration of a filter in a proxy, shared by all the stream filter
instances using it. The same class may be instantiated several times in a
proxy, with different ids, if the class allows it. This is described in
section 3.2.

This document uses "filter instance" for the configuration entity, which is
user-visible, and "stream filter instance" for the per-stream entity, which is
attached to a stream and represented by 'struct filter' in the code.

In legacy mode, the filter configurations are attached to the proxy and the
declaration order defines the evaluation order. In the new mode, the order is
defined by the class placement and can be refined per proxy with
"filter-sequence". This is described in section 2.

A stream filter instance is the per-stream object attached to a stream and
passed as argument to the callbacks. It carries a partially opaque context (or
a state) owned by the filter, making the instance stateful. Depending the
filter is declared in a frontend or a backend section, its stream filter
instances will be created, respectively, when a stream is created or when a
backend is selected. Their behaviors will also be different. Only stream filter
instances of filters declared in a frontend section will be aware of the
creation and the destruction of the stream, and will take part in the channels
analyzing before the backend is defined.

It is important to remember the configuration of a filter is shared by all its
stream filter instances, while the context of a stream filter instance is owned
by a single stream.

Filters are designed to be chained. It is possible to declare several filters in
the same proxy section. The evaluation order is the declaration order in legacy
mode, and the class order - possibly refined with "filter-sequence" - in the
new mode. Frontend and backend filters are also chained, frontend ones called
first. Even if the filters processing is serialized, each filter will behave
as it was alone (unless it was developed to be aware of other filters). For
all that, some constraints are imposed to filters, especially when data
exchanged between the client and the server are processed. We will discuss
again these constraints when we will tackle the subject of writing a filter.



2. HOW TO USE FILTERS
---------------------

2.1. LEGACY DECLARATIONS

To use a filter, the parameter 'filter' should be used, followed by the filter
name and, optionally, its configuration in the desired listen, frontend or
backend section. For instance :

    listen test
        ...
        filter trace name TST
        ...


Note that additional parameters on the filter line must be parsed by the filter
itself. The "filter" directive is only supported by filters registering a
keyword with flt_register_keywords() (see section 3.2.2).

The list of available filter classes is reported by 'haproxy -vv' :

    $> haproxy -vv
    HAProxy version 1.7-dev2-3a1d4a-33 2016/03/21
    Copyright 2000-2016 Willy Tarreau <willy@haproxy.org>

    [...]

    Available filter classes :
            trace, cache-store, decomp-req, decomp-res, spoe, lua,
            http-comp-req, http-comp-res, bwlim-in, bwlim-out, fcgi


Multiple filter lines can be used in a proxy section to chain filters. Filters
will be called in the declaration order.

Some filters can support implicit declarations in certain circumstances
(without the filter line). This is not recommended for new features but is
useful for existing ones moved in a filter, for backward compatibility
reasons. In legacy mode, implicit declarations are supported when there is only
one filter used on a proxy. When several filters are used, explicit
declarations are mandatory. The HTTP compression filter is one of these
filters. Alone, using 'compression' keywords is enough to use it. But when at
least a second filter is used, a filter line must be added.

    # filter line is optional
    listen t1
        bind *:80
        compression algo gzip
        compression offload
        server srv x.x.x.x:80

    # filter line is mandatory for the compression filter
    listen t2
        bind *:81
        filter trace name T2
        filter compression
        compression algo gzip
        compression offload
        server srv x.x.x.x:80


2.2. FILTER INSTANCES

The new mode is based on filter instances. Each instance belongs to a
filter class and may have an id. It is declared with the "filter-config"
directive :

    listen test
        ...
        filter-config trace id TST enabled
        ...

The syntax is the following one :

    filter-config <class> [ id <id> ] [ enabled ] [ <arguments> ... ]

  * '<class>' is the filter class name, registered by the filter (see section
    3.2).

  * '<id>' is mandatory for classes allowing several instances in the same
    proxy. It is used to refer to the instance in the other directives.

  * 'enabled' activates the instance. Without it, the instance is created
    but disabled. It may be activated later with "filter-enable".

  * '<arguments>' are the arguments of the filter class. See the filter
    documentation for details.

Several filter instances of the same class may be declared in the same proxy,
as long as the class supports it and each filter instance has an id. Note that
in legacy mode, each "filter" line also creates an independent configuration,
but the filter itself is responsible for rejecting duplicates: the class
capability and the instance ids do not exist in this mode. Filter instances are
also inherited from the defaults section: an instance declared locally with the
same class and id replaces the inherited one, and an explicit instance replaces
an implicitly declared one.

The filter instances of a proxy may be enabled or disabled at any time with :

    filter-enable  <class>[/<id>] [<class>[/<id>] ...]
    filter-disable <class>[/<id>] [<class>[/<id>] ...]

Without an id, all the instances of the class are updated.

By default, filters are evaluated following the class order (see section 3.2).
The order may be changed, per proxy and independently for the request and
response channels, with the "filter-sequence" directive :

    filter-sequence {global|request|response} <instance>:<pos>(<entity>) [<instance>:<pos>(<entity>) ...]

where '<instance>' is the instance to move (class[/id]), '<pos>' is "before" or
"after" and '<entity>' is a class or an instance (class[/id]) used as the
reference. "A:before(B)" means the instance A is evaluated before the entity
B, "A:after(B)" means A is evaluated after B. For instance, to evaluate the
"cache-store" instance with the id "content" before the "spoe" class :

    filter-sequence response cache-store/content:before(spoe)

The "global" order only drives the side-independent operations (configuration
checks, initialization and destruction). Moving an instance in this order does
not change its request or response positions.

The filter instances of a proxy and their evaluation orders may be inspected at
runtime with the CLI :

    show filter classes [global|request|response]
    show filter instances <px> [global|request|response]
    show filter instance <px> <class>[/<id>]

A filter may also be implicitly declared by another directive, for instance by
the options of the feature it implements. In the new mode, use
flt_add_implicit_instance() from the directive parser (see section 3.2.1):
it creates an enabled instance, with an optional id, that an explicit
"filter-config" declaration may replace. Unlike the legacy mode, an implicit
declaration may coexist with other filters in the same proxy.




3. HOW TO WRITE A NEW FILTER
----------------------------

To write a filter, there are 2 header files to explore :

  * include/haproxy/filters-t.h : This is the main header file, containing all
                                  important structures to use. It represents the
                                  filter API.

  * include/haproxy/filters.h : This header file contains helper functions that
                                may be used. It also contains the internal API
                                used by HAProxy to handle filters.

To ease the filters integration, it is better to follow some conventions :

  * Use 'flt_' prefix to name the filter (e.g flt_http_comp or flt_trace).

  * Keep everything related to the filter in a same file.

The filter 'trace' can be used as a template to write new filter. It is a good
start to see how filters really work.

3.1. API OVERVIEW
----------------

Writing a filter can be summarized to write functions and attach them to the
existing callbacks. Available callbacks are listed in the following structure :

    struct flt_ops {
        /*
         * Callbacks to manage the filter lifecycle
         */
        int  (*init)             (struct proxy *p, struct flt_conf *fconf);
        void (*deinit)           (struct proxy *p, struct flt_conf *fconf);
        int  (*check)            (struct proxy *p, struct flt_conf *fconf);
        int  (*init_per_thread)  (struct proxy *p, struct flt_conf *fconf);
        void (*deinit_per_thread)(struct proxy *p, struct flt_conf *fconf);

        /*
         * Stream callbacks
         */
        int  (*attach)            (struct stream *s, struct filter *f);
        int  (*stream_start)      (struct stream *s, struct filter *f);
        int  (*stream_set_backend)(struct stream *s, struct filter *f, struct proxy *be);
        void (*stream_stop)       (struct stream *s, struct filter *f);
        void (*detach)            (struct stream *s, struct filter *f);
        void (*check_timeouts)    (struct stream *s, struct filter *f);

        /*
         * Channel callbacks
         */
        int  (*channel_start_analyze)(struct stream *s, struct filter *f,
                                      struct channel *chn);
        int  (*channel_pre_analyze)  (struct stream *s, struct filter *f,
                                      struct channel *chn,
                                      unsigned int an_bit);
        int  (*channel_post_analyze) (struct stream *s, struct filter *f,
                                      struct channel *chn,
                                      unsigned int an_bit);
        int  (*channel_end_analyze)  (struct stream *s, struct filter *f,
                                      struct channel *chn);

        /*
         * HTTP callbacks
         */
        int  (*http_headers)       (struct stream *s, struct filter *f,
                                    struct http_msg *msg);
        int  (*http_payload)       (struct stream *s, struct filter *f,
                                    struct http_msg *msg, unsigned int offset,
                                    unsigned int len);
        int  (*http_end)           (struct stream *s, struct filter *f,
                                    struct http_msg *msg);

        void (*http_reset)         (struct stream *s, struct filter *f,
                                    struct http_msg *msg);
        void (*http_reply)         (struct stream *s, struct filter *f,
                                    short status,
                                    const struct buffer *msg);

        /*
         * TCP callbacks
         */
        int  (*tcp_payload)     (struct stream *s, struct filter *f,
                                 struct channel *chn, unsigned int offset,
                                 unsigned int len);
    };


We will explain in following parts when these callbacks are called and what they
should do.

Filters are declared in proxy sections. So each proxy has an ordered list of
filters, possibly empty if no filter is used. In legacy mode, each "filter"
line represents an entry in this list: the filter configurations are stored in
the field 'filter_configs' of the 'proxy' structure, each one of type
'struct flt_conf *'. In the new mode, the filter instances are stored
separately and the core builds the evaluation orders from them, but a filter
still only deals with 'struct flt_conf' once its filter instance is finalized.

    /*
     * Structure representing the filter configuration, attached to a proxy and
     * accessible from a filter when instantiated in a stream
     */
    struct flt_conf {
        const char     *name;   /* The filter name */
        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; /* Internal link in the request order */
        struct list     by_res; /* Internal link in the response order */
    };

  * 'flt_conf.name' is the filter class name of the configuration. In legacy
    mode, it may be set by the parsing function. It is not used by HAProxy.

  * 'flt_conf.id' is an identifier, defined by the filter. It can be NULL. It
    is used by the core to look a filter configuration up by its id, with
    flt_find_conf() (see section 3.7), so in the new mode the class parse
    callback must set it like the legacy parsing function does. Filters can
    also use it in log messages or as a uniq identifier to check multiple
    declarations. HAProxy never frees or duplicates it: it is the filter
    responsibility to free it, if necessary, once the configuration is
    released.

  * 'flt_conf.conf' is opaque. It is the internal configuration of a filter,
    generally allocated and filled by its parsing function (see sections 3.2.1
    and 3.2.2). It is the filter responsibility to free it.

  * 'flt_conf.ops' references the callbacks implemented by the filter. This
    field must be set during the parsing phase (see section 3.2) and can be
    refined during the initialization phase (see section 3.3). If it is
    dynamically allocated, it is the filter responsibility to free it. In the
    new mode, it is mandatory: an instance whose class parse callback does not
    set it is rejected at startup.

  * 'flt_conf.flags' is a bitfield to specify the filter capabilities. For now,
    only FLT_CFG_FL_HTX may be set when a filter is able to process HTX
    streams. If not set, the filter is excluded from the HTTP filtering.

  * 'flt_conf.list', 'flt_conf.by_req' and 'flt_conf.by_res' are internal
    links, managed by the core. Filters must not use them.


The filter configuration is global and shared by all its stream filter
instances. A stream filter instance is created in the context of a stream and
attached to this stream. In the structure 'stream', the field 'strm_flt' is the
state of all stream filter instances attached to a stream. The per-channel
state is stored in the 'flt' field of each 'channel' (a 'struct chn_flt') :

    /*
     * Structure representing the "global" state of filters attached to a
     * stream.
     */
    struct strm_flt {
        struct list    filters;               /* List of filters attached to a stream */
        unsigned short flags;                 /* STRM_FL_* */
    };

    /* structure holding filter state for some members that are channel oriented */
    struct chn_flt {
        struct list filters;                  /* List of filters attached to a channel */
        struct filter *current;               /* From which filter resume processing, for a specific channel. */
        unsigned char nb_data_filters;        /* Number of data filters registered on channel */
        unsigned long long offset;
    };


Stream filter instances attached to a stream are stored in the field
'strm_flt.filters', each one is of type 'struct filter *'. The same stream
filter instance is linked in the request and/or the response filter list of
the channels, depending on the sides supported by its class or instance:

    /*
     * Structure representing a stream filter instance attached to a stream
     */
    struct filter {
        struct flt_conf *config;           /* the filter's configuration */
        void           *ctx;               /* The filter context (opaque) */
        unsigned short  flags;             /* FLT_FL_* */
        unsigned int    calls;             /* number of calls */
        unsigned long long offset[2];      /* Offset of input data already filtered for a specific channel
                                            * 0: request channel, 1: response channel */
        unsigned int    pre_analyzers;     /* bit field indicating analyzers to pre-process */
        unsigned int    post_analyzers;    /* bit field indicating analyzers to post-process */
        struct list     list;              /* Filter list for the stream */
        struct list     req_list;          /* Filter list for request channel */
        struct list     res_list;          /* Filter list for response channel */
    };

  * 'filter.config' is the filter configuration previously described. All the
    stream filter instances of a proxy share it. In the new mode, a filter
    instance may be instantiated on the request channel, the response channel,
    or both, following the class placement and the instance state.

  * 'filter.ctx' is an opaque context. It is managed by the filter, so it is its
    responsibility to free it.

  * 'filter.pre_analyzers' and 'filter.post_analyzers' will be described later
    (see section 3.5).

  * 'filter.offset' will be described later (see section 3.6).


3.2. DEFINING THE FILTER CLASS AND ITS CONFIGURATION
-----------------------------------------------------

To make a filter usable in the new mode, its class must be declared, registered
and placed, and a parse callback must be provided. To keep the legacy mode
working, a keyword parser must also be registered (see section 3.2.2).

The filter class is described by the following structure :

    struct filter_class {
        const char *name;           /* The filter class name, must be uniq */
        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 */
    };

  * 'filter_class.name' is the name used by the "filter-config" directive and
    the other "filter-*" directives.

  * 'filter_class.flags' is a bitfield to specify the class capabilities. For
    now, only FLT_CLS_FL_MULTI may be set when several instances of the class
    are allowed in the same proxy. In this case, an id is mandatory for each
    instance. Otherwise a second instance of the class in the same proxy is
    rejected, even with a different id.

  * 'filter_class.parse' is the callback parsing a filter instance (see
    section 3.2.1). It may be NULL, but in this case the class cannot be used
    with "filter-config" at all.

  * 'filter_class.req' and 'filter_class.res' anchor the class in the request
    and response class orders (see below).

  * 'filter_class.list' links the class in the global registry.

The class is registered with filter_register_class(). It must then be placed,
on each side it supports, relative to an already placed class, with
filter_place_class() :

    static struct filter_class flt_my_filter_cls;

    static int my_filter_register_class(void)
    {
        if (filter_register_class(&flt_my_filter_cls, "my-filter", my_filter_parse_instance) < 0)
            return -1;

        /* Evaluate the filter before SPOE on the request path and after the Lua
         * filters on the response path. */
        if (filter_place_class(&flt_my_filter_cls, FLT_SIDE_REQ, "spoe", FLT_POS_BEFORE) < 0)
            return -1;
        if (filter_place_class(&flt_my_filter_cls, FLT_SIDE_RES, "lua", FLT_POS_AFTER) < 0)
            return -1;
        return 0;
    }
    INITCALL1(STG_INIT, my_filter_register_class);

filter_register_class_full() is a helper for the common case where the class is
placed on both sides at the same position relative to the same class.

A class placed on neither side is rejected at startup because it would be
useless. A class may be placed on both sides with different positions. The
placement defines the default evaluation order of the instances of the class
relative to the other classes. The global order follows the class registration
order.

WARNING : filter_register_class() must be called after the built-in classes
          were initialized, so from an initcall of the STG_INIT stage or a later
          one. Otherwise the class registration fails.


Then the filter internal configuration must be defined. For instance :

    struct my_filter_config {
        struct proxy *proxy;
        char         *name;
        /* ... */
    };


All callbacks implemented by the filter must then be declared. Here, a global
variable is used :

    struct flt_ops my_filter_ops {
        .init   = my_filter_init,
        .deinit = my_filter_deinit,
        .check  = my_filter_config_check,

        /* ... */
     };


3.2.1. THE FILTER CLASS PARSE CALLBACK

The class parse callback is called once per filter instance, after the whole
configuration was parsed, to produce the filter configuration attached to the
filter instance ('inst->fconf'). 'inst->conf.argv' contains the arguments of
the instance, i.e. the "filter-config" arguments following the class name and
the optional "id <id>" and "enabled" tokens. They are NULL-terminated :

    /* Return 0 on success, else -1 with <err> filled */
    static int
    my_filter_parse_instance(char **args, struct proxy *px,
                             struct filter_instance *inst, char **err)
    {
        struct my_filter_config *my_conf;

        /* All the arguments must be consumed by the callback */
        if (*args) {
            memprintf(err, "'%s' : unexpected argument '%s'",
                      inst->class->name, args[0]);
            return -1;
        }

        /* Allocate the internal configuration used by the filter */
        my_conf = calloc(1, sizeof(*my_conf));
        if (!my_conf) {
            memprintf(err, "out of memory");
            return -1;
        }
        my_conf->proxy = px;

        /* ... */

        /* Set the filter id and the callbacks. inst->fconf was allocated by
         * HAProxy and is never NULL here. */
        inst->fconf->id   = my_filter_flt_id;
        inst->fconf->ops  = &my_filter_ops;

        /* Last, save the internal configuration */
        inst->fconf->conf = my_conf;

        /* If the filter supports HTX streams */
        inst->fconf->flags |= FLT_CFG_FL_HTX;
        return 0;
    }

The filter instance is passed to the callback and is the only chance to read
its attributes: its id ('inst->id'), its flags ('inst->flags') and the class it
belongs to ('inst->class'). The following flags may be set :

  * 'FLT_INST_F_IMPLICIT' : the instance was created implicitly by another
    directive with flt_add_implicit_instance() (see below).

  * 'FLT_INST_F_INHERITED' : the instance was inherited from the defaults
    section of the proxy.

The constraints below must be kept in mind while writing the callback :

  * 'inst->fconf->ops' must be set, otherwise the configuration is rejected at
    startup. All the arguments of the instance must be consumed.

  * 'inst->fconf->conf' is owned by the filter. It is released by the filter,
    generally in the 'flt_ops.deinit' callback (see section 3.3).

  * The filter instance is a shared, read-only object once the startup is
    completed: it is used to instantiate the filter for all the streams of the
    proxy. Never store per-stream data in the filter instance or in its
    configuration. Use 'filter.ctx' for that purpose.

  * The callback is called after the configuration was fully parsed and after
    the filter instances of the defaults section were inherited. It is not
    called while the "filter-config" directive is read. As a consequence, other
    directives must not keep pointers to filter instances during the parsing:
    they must resolve the filter configuration at the check stage, using
    flt_find_conf() and flt_foreach_conf() (see section 3.7).

  * Side effects on the proxy options (for instance enabling a channel
    direction) are allowed but must be idempotent, because the callback is also
    called for inherited and implicit filter instances.

  * For implicit filter instances (FLT_INST_F_IMPLICIT), the callback must not
    re-enable an option that the directive did not enable, and must not add
    side effects: it only finalizes the filter configuration. The filter is
    already enabled by the directive and an explicit declaration wins over an
    implicit one.

  * For inherited filter instances (FLT_INST_F_INHERITED), the callback is
    called again in the child proxy. Capability-dependent fields must be left
    unset if the final proxy does not support them. In this case, the instance
    is kept as metadata but the filter can simply decline its attachment to the
    streams by returning 0 in 'flt_ops.attach'.

  * A filter instance is instantiated on a stream only if it is enabled, in
    which case a stream filter instance is created. It is enabled by the
    "enabled" token, by the directive that created it implicitly or by
    "filter-enable". Otherwise it is disabled by default.

  * The id and the class name may only contain alphanumeric characters, '.',
    '_' and '-'. In the configuration and the CLI, a filter instance is
    referred to as <class>/<id>.

Filters that are enabled by another directive (the compression options, the
cache rules, ...) should create their filter instance with
flt_add_implicit_instance(). It may be called during the configuration
parsing, from the directive that enables the filter :

    flt_add_implicit_instance(px, cls, "my-id", args, file, line);

where 'cls' is the filter class, 'args' the arguments of the filter instance
(or NULL), and 'file'/'line' the location of the directive. The instance is
created enabled and marked FLT_INST_F_IMPLICIT.


3.2.2. THE LEGACY KEYWORD PARSER

To keep the support of the legacy "filter" directive, the filter must register
its name as a valid keyword on the filter line. This is the historical way to
declare a filter and it is optional: a filter supporting only the new mode
cannot be used with the "filter" directive, and no other directive may
transitively enable it in legacy mode either.

    /* Declare the filter parser for "my_filter" keyword */
    static struct flt_kw_list flt_kws = { "MY_FILTER_SCOPE", { }, {
            { "my_filter", parse_my_filter_cfg, NULL /* private data */ },
            { NULL, NULL, NULL },
        }
    };
    INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);


The function to parse the filter configuration must be written, here
'parse_my_filter_cfg'. This function must parse all remaining keywords on the
filter line :

    /* Return -1 on error, else 0 */
    static int
    parse_my_filter_cfg(char **args, int *cur_arg, struct proxy *px,
                        struct flt_conf *flt_conf, char **err, void *private)
    {
        struct my_filter_config *my_conf;
        int pos = *cur_arg;

        /* Allocate the internal configuration used by the filter */
        my_conf = calloc(1, sizeof(*my_conf));
        if (!my_conf) {
            memprintf(err, "%s : out of memory", args[*cur_arg]);
            return -1;
        }
        my_conf->proxy = px;

        /* ... */

        /* Parse all keywords supported by the filter and fill the internal
         * configuration */
        pos++; /* Skip the filter name */
        while (*args[pos]) {
            if (!strcmp(args[pos], "name")) {
                if (!*args[pos + 1]) {
                    memprintf(err, "'%s' : '%s' option without value",
                              args[*cur_arg], args[pos]);
                              goto error;
                }
                my_conf->name = strdup(args[pos + 1]);
                if (!my_conf->name) {
                    memprintf(err, "%s : out of memory", args[*cur_arg]);
                    goto error;
                }
                pos += 2;
            }

            /* ... parse other keywords ... */
        }
        *cur_arg = pos;

        /* Set callbacks supported by the filter */
        flt_conf->ops  = &my_filter_ops;

        /* Last, save the internal configuration */
        flt_conf->conf = my_conf;
        return 0;

      error:
         if (my_conf->name)
            free(my_conf->name);
        free(my_conf);
        return -1;
    }


WARNING : In this parsing function, 'flt_conf->ops' must be initialized. All
          arguments of the filter line must also be parsed. This is mandatory.

In the previous example, the filter line should be read as follows :

    filter my_filter name MY_NAME ...

A filter supporting both modes should not duplicate the parsing code. The class
parse callback may delegate the instance arguments to the legacy keyword
parser with flt_parse_instance_legacy() :

    /* Return 0 on success, else -1 with <err> filled */
    static int
    my_filter_parse_instance(char **args, struct proxy *px,
                             struct filter_instance *inst, char **err)
    {
        return flt_parse_instance_legacy(px, inst, err, "my_filter",
                                         parse_my_filter_cfg, NULL);
    }

This helper rebuilds the argument vector expected by the legacy parser (the
keyword followed by the arguments, terminated by an empty string) from
'inst->conf.argv' and checks that all the arguments were consumed.

WARNING : The legacy parser may rely on parsing-time globals, the most common
          one being 'curproxy'. Such globals are not set when the class parse
          callback is called (after the configuration parsing), so they must be
          saved and restored around the call by the filter if needed.


Optionally, by implementing the 'flt_ops.check' callback, an extra check is
added for the internal configuration of the filter after the parsing phase,
when the HAProxy configuration is fully defined. It is called once per proxy,
for all the finalized filter instances in the new mode, or for all the legacy
configurations otherwise. For instance :

    /* Check configuration of a trace filter for a specified proxy.
     * Return 1 on error, else 0. */
    static int
    my_filter_config_check(struct proxy *px, struct flt_conf *my_conf)
    {
        if (px->mode != PR_MODE_HTTP) {
            ha_alert("The filter 'my_filter' cannot be used in non-HTTP mode.\n");
            return 1;
        }

        /* ... */

        return 0;
    }



3.3. MANAGING THE FILTER LIFECYCLE
----------------------------------

Once the configuration is parsed and checked, filters are ready to be used.
There are two main callbacks to manage the filter lifecycle :

  * 'flt_ops.init' : It initializes the filter for a proxy. This callback may be
                     defined to finish the filter configuration.

  * 'flt_ops.deinit' : It cleans up what the parsing function and the init
                       callback have done. This callback is useful to release
                       memory allocated for the filter configuration.

In the new mode, these callbacks are called once per filter instance, in the
global order, for all the filter instances of the proxy - including the
disabled ones. The 'enabled' state only controls whether a filter instance is
instantiated on streams. The filter configuration ('flt_conf') is owned by the
filter instance and is released with it when the proxy is destroyed.

Here is an example :

    /* Initialize the filter. Returns -1 on error, else 0. */
    static int
    my_filter_init(struct proxy *px, struct flt_conf *fconf)
    {
        struct my_filter_config *my_conf = fconf->conf;

        /* ... */

        return 0;
    }

    /* Free resources allocated by the trace filter. */
    static void
    my_filter_deinit(struct proxy *px, struct flt_conf *fconf)
    {
        struct my_filter_config *my_conf = fconf->conf;

        if (my_conf) {
            free(my_conf->name);
            /* ... */
            free(my_conf);
        }
        fconf->conf = NULL;
    }


3.3.1. DEALING WITH THREADS
--------------------------

When HAProxy is compiled with the threads support and started with more that one
thread (global.nbthread > 1), then it is possible to manage the filter per
thread with following callbacks :

  * 'flt_ops.init_per_thread': It initializes the filter for each thread. It
                               works the same way than 'flt_ops.init' but in the
                               context of a thread. This callback is called
                               after the thread creation.

  * 'flt_ops.deinit_per_thread': It cleans up what the init_per_thread callback
                                 have done. It is called in the context of a
                                 thread, before exiting it.

It is the filter responsibility to deal with concurrency. check, init and deinit
callbacks are called on the main thread. All others are called on a "worker"
thread (not always the same). It is also the filter responsibility to know if
HAProxy is started with more than one thread. If it is started with one thread
(or compiled without the threads support), these callbacks will be silently
ignored (in this case, global.nbthread will be always equal to one).

For a proxy dynamically created at runtime (with the "add backend" CLI
command), the lifecycle follows the same stages: the check and init callbacks
run on the thread performing the creation, the per-thread callbacks are
deferred to every thread, and the deinit callbacks run on the thread
performing the deletion, once every thread ran its per-thread callbacks.


3.4. HANDLING THE STREAMS ACTIVITY
-----------------------------------

It may be interesting to handle streams activity. For now, there is three
callbacks that should define to do so :

  * 'flt_ops.stream_start' : It is called when a stream is started. This
                             callback can fail by returning a negative value. It
                             will be considered as a critical error by HAProxy
                             which disabled the listener for a short time.

  * 'flt_ops.stream_set_backend' : It is called when a backend is set for a
                                   stream. This callbacks will be called for all
                                   filters attached to a stream (frontend and
                                   backend). Note this callback is not called if
                                   the frontend and the backend are the same.

  * 'flt_ops.stream_stop' : It is called when a stream is stopped. This callback
                            always succeed. Anyway, it is too late to return an
                            error.

For instance :

    /* Called when a stream is created. Returns -1 on error, else 0. */
    static int
    my_filter_stream_start(struct stream *s, struct filter *filter)
    {
         struct my_filter_config *my_conf = FLT_CONF(filter);

         /* ... */

         return 0;
    }

    /* Called when a backend is set for a stream */
    static int
    my_filter_stream_set_backend(struct stream *s, struct filter *filter,
                                 struct proxy *be)
    {
         struct my_filter_config *my_conf = FLT_CONF(filter);

        /* ... */

        return 0;
    }

    /* Called when a stream is destroyed */
    static void
    my_filter_stream_stop(struct stream *s, struct filter *filter)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

         /* ... */
    }


WARNING : Handling the streams creation and destruction is only possible for
          filters defined on proxies with the frontend capability.

In addition, it is possible to handle creation and destruction of stream filter
instances using following callbacks:

  * 'flt_ops.attach' : It is called after a stream filter instance creation,
                       when it is attached to a stream. This happens when the
                       stream is started for filters defined on the stream's
                       frontend and when the backend is set for filters
                       declared on the stream's backend. It is possible to
                       ignore the filter, if needed, by returning 0. This
                       could be useful to have conditional filtering.

  * 'flt_ops.detach' : It is called when a stream filter instance is detached
                       from a stream, before its destruction. This happens when
                       the stream is stopped for filters defined on the
                       stream's frontend and when the analyze ends for filters
                       defined on the stream's backend.

For instance :

    /* Called when a stream filter instance is created and attached to a stream */
    static int
    my_filter_attach(struct stream *s, struct filter *filter)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        if (/* ... */)
            return 0; /* Ignore the filter here */
        return 1;
    }

    /* Called when a stream filter instance is detached from a stream, just
     * before its destruction */
    static void
    my_filter_detach(struct stream *s, struct filter *filter)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        /* ... */
    }

Finally, it may be interesting to notify the filter when the stream is woken up
because of an expired timer. This could let a chance to check some internal
timeouts, if any. To do so the following callback must be used :

  * 'flt_opt.check_timeouts' : It is called when a stream is woken up because of
                               an expired timer.

For instance :

    /* Called when a stream is woken up because of an expired timer */
    static void
    my_filter_check_timeouts(struct stream *s, struct filter *filter)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        /* ... */
    }


3.5. ANALYZING THE CHANNELS ACTIVITY
------------------------------------

The main purpose of filters is to take part in the channels analyzing. To do so,
there is 2 callbacks, 'flt_ops.channel_pre_analyze' and
'flt_ops.channel_post_analyze', called respectively before and after each
analyzer attached to a channel, except analyzers responsible for the data
forwarding (TCP or HTTP). Concretely, on the request channel, these callbacks
could be called before following analyzers :

  * tcp_inspect_request        (AN_REQ_INSPECT_FE and AN_REQ_INSPECT_BE)
  * http_wait_for_request      (AN_REQ_WAIT_HTTP)
  * http_wait_for_request_body (AN_REQ_HTTP_BODY)
  * http_process_req_common    (AN_REQ_HTTP_PROCESS_FE)
  * process_switching_rules    (AN_REQ_SWITCHING_RULES)
  * http_process_req_ common   (AN_REQ_HTTP_PROCESS_BE)
  * http_process_tarpit        (AN_REQ_HTTP_TARPIT)
  * process_server_rules       (AN_REQ_SRV_RULES)
  * http_process_request       (AN_REQ_HTTP_INNER)
  * tcp_persist_rdp_cookie     (AN_REQ_PRST_RDP_COOKIE)
  * process_sticking_rules     (AN_REQ_STICKING_RULES)

And on the response channel :

  * tcp_inspect_response     (AN_RES_INSPECT)
  * http_wait_for_response   (AN_RES_WAIT_HTTP)
  * process_store_rules      (AN_RES_STORE_RULES)
  * http_process_res_common  (AN_RES_HTTP_PROCESS_BE)

Unlike the other callbacks previously seen before, 'flt_ops.channel_pre_analyze'
can interrupt the stream processing. So a filter can decide to not execute the
analyzer that follows and wait the next iteration. If there are more than one
filter, following ones are skipped. On the next iteration, the filtering resumes
where it was stopped, i.e. on the filter that has previously stopped the
processing. So it is possible for a filter to stop the stream processing on a
specific analyzer for a while before continuing. Moreover, this callback can be
called many times for the same analyzer, until it finishes its processing. For
instance :

    /* Called before a processing happens on a given channel.
     * Returns a negative value if an error occurs, 0 if it needs to wait,
     * any other value otherwise. */
    static int
    my_filter_chn_pre_analyze(struct stream *s, struct filter *filter,
                              struct channel *chn, unsigned an_bit)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        switch (an_bit) {
            case AN_REQ_WAIT_HTTP:
                if (/* wait that a condition is verified before continuing */)
                    return 0;
                break;
            /* ... * /
        }
        return 1;
    }

  * 'an_bit' is the analyzer id. All analyzers are listed in
    'include/haproxy/channels-t.h'.

  * 'chn' is the channel on which the analyzing is done. It is possible to
    determine if it is the request or the response channel by testing if
    CF_ISRESP flag is set :

      │ ((chn->flags & CF_ISRESP) == CF_ISRESP)


In previous example, the stream processing is blocked before receipt of the HTTP
request until a condition is verified.

'flt_ops.channel_post_analyze', for its part, is not resumable. It returns a
negative value if an error occurs, any other value otherwise. It is called when
a filterable analyzer finishes its processing, so once for the same analyzer.
For instance :

    /* Called after a processing happens on a given channel.
     * Returns a negative value if an error occurs, any other
     * value otherwise. */
    static int
    my_filter_chn_post_analyze(struct stream *s, struct filter *filter,
                               struct channel *chn, unsigned an_bit)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);
        struct http_msg         *msg;

        switch (an_bit) {
            case AN_REQ_WAIT_HTTP:
                if (/* A test on received headers before any other treatment */) {
                    msg = ((chn->flags & CF_ISRESP) ? &s->txn.http->rsp : &s->txn.http->req);
                    txn->status = 400;
                    msg->msg_state = HTTP_MSG_ERROR;
                    http_reply_and_close(s, s->txn.http->status, http_error_message(s));
                    return -1; /* This is an error ! */
                }
                break;
            /* ... * /
        }
        return 1;
    }


Pre and post analyzer callbacks of a filter are not automatically called. They
must be regiesterd explicitly on analyzers, updating the value of
'filter.pre_analyzers' and 'filter.post_analyzers' bit fields. All analyzer bits
are listed in 'include/types/channels.h'. Here is an example :

    static int
    my_filter_stream_start(struct stream *s, struct filter *filter)
    {
        /* ... * /

        /* Register the pre analyzer callback on all request and response
         * analyzers */
        filter->pre_analyzers |= (AN_REQ_ALL | AN_RES_ALL)

        /* Register the post analyzer callback of only on AN_REQ_WAIT_HTTP and
         * AN_RES_WAIT_HTTP analyzers */
         filter->post_analyzers |= (AN_REQ_WAIT_HTTP | AN_RES_WAIT_HTTP)

        /* ... * /
        return 0;
    }


To surround activity of a filter during the channel analyzing, two new analyzers
has been added :

  * 'flt_start_analyze' (AN_REQ/RES_FLT_START_FE/AN_REQ_RES_FLT_START_BE) : For
    a specific filter, this analyzer is called before any call to the
    'channel_analyze' callback. From the filter point of view, it calls the
    'flt_ops.channel_start_analyze' callback.

  * 'flt_end_analyze' (AN_REQ/RES_FLT_END) : For a specific filter, this
    analyzer is called when all other analyzers have finished their
    processing. From the filter point of view, it calls the
    'flt_ops.channel_end_analyze' callback.

These analyzers are called only once per streams.

'flt_ops.channel_start_analyze' and 'flt_ops.channel_end_analyze' callbacks can
interrupt the stream processing, as 'flt_ops.channel_analyze'. Here is an
example :

    /* Called when analyze starts for a given channel
     * Returns a negative value if an error occurs, 0 if it needs to wait,
     * any other value otherwise. */
    static int
    my_filter_chn_start_analyze(struct stream *s, struct filter *filter,
                                struct channel *chn)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        /* ... TODO ... */

        return 1;
    }

    /* Called when analyze ends for a given channel
     * Returns a negative value if an error occurs, 0 if it needs to wait,
     * any other value otherwise. */
    static int
    my_filter_chn_end_analyze(struct stream *s, struct filter *filter,
                              struct channel *chn)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        /* ... TODO ... */

        return 1;
    }


Workflow on channels can be summarized as following :

   FE: Called for filters defined on the stream's frontend
   BE: Called for filters defined on the stream's backend

                                          +------->---------+
                |                         |                 |
    +----------------------+              |      +----------------------+
    |  flt_ops.attach (FE) |              |      |  flt_ops.attach (BE) |
    +----------------------+              |      +----------------------+
                |                         |                  |
                V                         |                  V
  +--------------------------+            | +------------------------------------+
  | flt_ops.stream_start (FE)|            | | flt_ops.stream_set_backend (FE+BE) |
  +--------------------------+            | +------------------------------------+
                |                         |                  |
               ...                        |                 ...
                |                         |                  |
                |                         ^                  |
                |                 --+     |                  |                 --+
                +------<----------+ |     |                  +--------<--------+ |
                |                 | |     |                  |                 | |
                V                 | |     |                  V                 | |
+-------------------------------+ | |     |  +-------------------------------+ | |
|      flt_start_analyze (FE)   +-+ |     |  |      flt_start_analyze (BE)   +-+ |
|(flt_ops.channel_start_analyze)|   | F   |  |(flt_ops.channel_start_analyze)|   |
+---------------+---------------+   | R   |  +-------------------------------+   |
                |                   | O   |                  |                   |
                +------<---------+  | N   ^                  +--------<-------+  | B
                |                |  | T   |                  |                |  | A
+---------------|------------+   |  | E   |  +---------------|------------+   |  | C
|+--------------V-------------+  |  | N   |  |+--------------V-------------+  |  | K
||+----------------------------+ |  | D   |  ||+----------------------------+ |  | E
|||flt_ops.channel_pre_analyze | |  |     |  |||flt_ops.channel_pre_analyze | |  | N
|||             V              | |  |     |  |||             V              | |  | D
|||        analyzer (FE)       +-+  |     |  |||     analyzer (FE+BE)       +-+  |
+||             V              |    |     |  +||             V              |    |
 +|flt_ops.channel_post_analyze|    |     |   +|flt_ops.channel_post_analyze|    |
  +----------------------------+    |     |    +----------------------------+    |
                |                 --+     |                  |                   |
                +------------>------------+                 ...                  |
                                                             |                   |
                                                 [ data filtering (see below) ]  |
                                                             |                   |
                                                            ...                  |
                                                             |                   |
                                                             +--------<--------+ |
                                                             |                 | |
                                                             V                 | |
                                             +-------------------------------+ | |
                                             |    flt_end_analyze (FE+BE)    +-+ |
                                             | (flt_ops.channel_end_analyze) |   |
                                             +---------------+---------------+   |
                                                             |                 --+
                                                             V
                                                  +----------------------+
                                                  |  flt_ops.detach (BE) |
                                                  +----------------------+
                                                             |
                                                             V
                                                +--------------------------+
                                                | flt_ops.stream_stop (FE) |
                                                +--------------------------+
                                                             |
                                                             V
                                                  +----------------------+
                                                  |  flt_ops.detach (FE) |
                                                  +----------------------+
                                                             |
                                                             V

By zooming on an analyzer box we have:

                     ...
                      |
                      V
                      |
                      +-----------<-----------+
                      |                       |
    +-----------------+--------------------+  |
    |                 |                    |  |
    |                 +--------<---------+ |  |
    |                 |                  | |  |
    |                 V                  | |  |
    |     flt_ops.channel_pre_analyze ->-+ |  ^
    |                 |                    |  |
    |                 |                    |  |
    |                 V                    |  |
    |              analyzer --------->-----+--+
    |                 |                    |
    |                 |                    |
    |                 V                    |
    |     flt_ops.channel_post_analyze     |
    |                 |                    |
    |                 |                    |
    +-----------------+--------------------+
                      |
                      V
                     ...


3.6. FILTERING THE DATA EXCHANGED
-----------------------------------

WARNING : To fully understand this part, it is important to be aware on how the
          buffers work in HAProxy. For the HTTP part, it is also important to
          understand how data are parsed and structured, and how the internal
          representation, called HTX, works. See doc/internals/buffer-api.txt
          and doc/internals/htx-api.txt for details.

An extended feature of the filters is the data filtering. By default a filter
does not look into data exchanged between the client and the server because it
is expensive. Indeed, instead of forwarding data without any processing, each
byte need to be buffered.

So, to enable the data filtering on a channel, at any time, in one of previous
callbacks, 'register_data_filter' function must be called. And conversely, to
disable it, 'unregister_data_filter' function must be called. For instance :

    my_filter_http_headers(struct stream *s, struct filter *filter,
                           struct http_msg *msg)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);

        /* 'chn' must be the request channel */
        if (!(msg->chn->flags & CF_ISRESP)) {
            struct htx *htx;
            struct ist hdr;
            struct http_hdr_ctx ctx;

            htx = htxbuf(msg->chn->buf);

            /* Enable the data filtering for the request if 'X-Filter' header
             * is set to 'true'. */
            hdr = ist("X-Filter);
            ctx.blk = NULL;
            if (http_find_header(htx, hdr, &ctx, 0) &&
                ctx.value.len >= 4 && memcmp(ctx.value.ptr, "true", 4) == 0)
                register_data_filter(s, chn, filter);
        }

        return 1;
    }

Here, the data filtering is enabled if the HTTP header 'X-Filter' is found and
set to 'true'.

If several filters are declared, the evaluation order remains the same,
regardless the order of the registrations to the data filtering. Data
registrations must be performed before the data forwarding step. However, a
filter may be unregistered from the data filtering at any time.

Depending on the stream type, TCP or HTTP, the way to handle data filtering is
different. HTTP data are structured while TCP data are raw. And there are more
callbacks for HTTP streams to fully handle all steps of an HTTP transaction. But
the main part is the same. The data filtering is performed in one callback,
called in loop on input data starting at a specific offset for a given
length. Data analyzed by a filter are considered as forwarded from its point of
view. Because filters are chained, a filter never analyzes more data than its
predecessors. Thus only data analyzed by the last filter are effectively
forwarded. This means, at any time, any filter may choose to not analyze all
available data (available from its point of view), blocking the data forwarding.

Internally, filters own 2 offsets representing the number of bytes already
analyzed in the available input data, one per channel. There is also an offset
couple at the stream level, in the strm_flt object, representing the total
number of bytes already forwarded. These offsets may be retrieved and updated
using following macros :

  * FLT_OFF(flt, chn)

  * FLT_STRM_OFF(s, chn)

where 'flt' is the 'struct filter' passed as argument in all callbacks, 's' the
filtered stream and 'chn' is the considered channel. However, there is no reason
for a filter to use these macros or take care of these offsets.


3.6.1. FILTERING DATA ON TCP STREAMS
-----------------------------------

The TCP data filtering for TCP streams is the easy case, because HAProxy do not
parse these data. Data are stored in raw in the buffer. So there is only one
callback to consider:

  * 'flt_ops.tcp_payload : This callback is called when input data are
    available. If not defined, all available data will be considered as analyzed
    and forwarded from the filter point of view.

This callback is called only if the filter is registered to analyze TCP
data. Here is an example :

    /* Returns a negative value if an error occurs, else the number of
     * consumed bytes. */
    static int
    my_filter_tcp_payload(struct stream *s, struct filter *filter,
                          struct channel *chn, unsigned int offset,
                          unsigned int len)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);
        int ret = len;

        /* Do not parse more than 'my_conf->max_parse' bytes at a time */
        if (my_conf->max_parse != 0 && ret > my_conf->max_parse)
            ret = my_conf->max_parse;

        /* if available data are not completely parsed, wake up the stream to
         * be sure to not freeze it. The best is probably to set a
         * chn->analyse_exp timer */
        if (ret != len)
            task_wakeup(s->task, TASK_WOKEN_MSG);
        return ret;
    }

But it is important to note that tunnelled data of an HTTP stream may also be
filtered via this callback. Tunnelled data are data exchange after an HTTP tunnel
is established between the client and the server, via an HTTP CONNECT or via a
protocol upgrade. In this case, the data are structured. Of course, to do so,
the filter must be able to parse HTX data and must have the FLT_CFG_FL_HTX flag
set. At any time, the IS_HTX_STRM() macros may be used on the stream to know if
it is an HTX stream or a TCP stream.


3.6.2. FILTERING DATA ON HTTP STREAMS
------------------------------------

The HTTP data filtering is a bit more complex because HAProxy data are
structutred and represented to an internal format, called HTX. So basically
there is the HTTP counterpart to the previous callback :

  * 'flt_ops.http_payload' : This callback is called when input data are
    available. If not defined, all available data will be considered as analyzed
    and forwarded for the filter.

But the prototype for this callbacks is slightly different. Instead of having
the channel as parameter, we have the HTTP message (struct http_msg). This
callback is called only if the filter is registered to analyze TCP data. Here is
an example :

    /* Returns a negative value if an error occurs, else the number of
     * consumed bytes. */
    static int
    my_filter_http_payload(struct stream *s, struct filter *filter,
                           struct http_msg *msg, unsigned int offset,
                           unsigned int len)
    {
        struct my_filter_config *my_conf = FLT_CONF(filter);
        struct htx *htx = htxbuf(&msg->chn->buf);
        struct htx_ret htxret = htx_find_offset(htx, offset);
        struct htx_blk *blk;

        blk = htxret.blk;
        offset = htxret.ret;
        for (; blk; blk = htx_get_next_blk(blk, htx)) {
            enum htx_blk_type type = htx_get_blk_type(blk);

            if (type == HTX_BLK_UNUSED)
                continue;
            else if (type == HTX_BLK_DATA) {
                /* filter data */
            }
            else
                break;
        }

        return len;
    }

In addition, there are two others callbacks :

  * 'flt_ops.http_headers' : This callback is called just before the HTTP body
    forwarding and after any processing on the request/response HTTP
    headers. When defined, this callback is always called for HTTP streams
    (i.e. without needs of a registration on data filtering).
    Here is an example :


        /* Returns a negative value if an error occurs, 0 if it needs to wait,
         * any other value otherwise. */
         static int
         my_filter_http_headers(struct stream *s, struct filter *filter,
                                struct http_msg *msg)
        {
            struct my_filter_config *my_conf = FLT_CONF(filter);
            struct htx *htx = htxbuf(&msg->chn->buf);
            struct htx_sl *sl = http_get_stline(htx);
            int32_t pos;

            for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
                struct htx_blk *blk = htx_get_blk(htx, pos);
                enum htx_blk_type type = htx_get_blk_type(blk);
                struct ist n, v;

                if (type == HTX_BLK_EOH)
                        break;
                if (type != HTX_BLK_HDR)
                        continue;

                n = htx_get_blk_name(htx, blk);
                v = htx_get_blk_value(htx, blk);
		/* Do something on the header name/value */
            }

            return 1;
        }

  * 'flt_ops.http_end' : This callback is called when the whole HTTP message was
    processed. It may interrupt the stream processing. So, it could be used to
    synchronize the HTTP request with the HTTP response, for instance :

        /* Returns a negative value if an error occurs, 0 if it needs to wait,
         * any other value otherwise. */
         static int
         my_filter_http_end(struct stream *s, struct filter *filter,
                            struct http_msg *msg)
         {
             struct my_filter_ctx *my_ctx = filter->ctx;


            if (!(msg->chn->flags & CF_ISRESP)) /* The request */
                my_ctx->end_of_req = 1;
            else /* The response */
                my_ctx->end_of_rsp = 1;

            /* Both the request and the response are finished */
            if (my_ctx->end_of_req == 1 && my_ctx->end_of_rsp == 1)
                return 1;

            /* Wait */
            return 0;
        }

Then, to finish, there are 2 informational callbacks :

  * 'flt_ops.http_reset' : This callback is called when an HTTP message is
    reset. This happens either when a 1xx informational response is received, or
    if we're retrying to send the request to the server after it failed. It
    could be useful to reset the filter context before receiving the true
    response.
    By checking s->txn.http->status, it is possible to know why this callback is
    called. If it's a 1xx, we're called because of an informational
    message. Otherwise, it is a L7 retry.

  * 'flt_ops.http_reply' : This callback is called when, at any time, HAProxy
    decides to stop the processing on a HTTP message and to send an internal
    response to the client. This mainly happens when an error or a redirect
    occurs.


3.6.3. REWRITING DATA
--------------------

The last part, and the trickiest one about the data filtering, is about the data
rewriting. For now, the filter API does not offer a lot of functions to handle
it. There are only functions to notify HAProxy that the data size has changed to
let it update internal state of filters. This is the developer responsibility to
update data itself, i.e. the buffer offsets, using following function :

  * 'flt_update_offsets()' : This function must be called when a filter alter
    incoming data. It updates offsets of the stream and of all filters
    preceding the calling one. Do not call this function when a filter change
    the size of incoming data leads to an undefined behavior.

A good example of filter changing the data size is the HTTP compression filter.


3.7. REFERENCING A FILTER CONFIGURATION FROM OTHER DIRECTIVES
-------------------------------------------------------------

A directive (a rule, an action, ...) may need to reference a filter, for
instance to share its configuration. It must not store a pointer to the filter
configuration during the parsing: in the new mode, the filter instances are
finalized during the post-parsing stage, after the rule was parsed, and they
may be inherited or replaced. References must be resolved later, from the check
callback of the rule ('act_rule.check_ptr'), which is called once the filter
instances were finalized.

The following helpers work in both modes and must be used for that purpose :

  * 'flt_has_filter(px)' returns true if the proxy uses at least one filter, in
    either mode. It replaces tests of 'px->filter_configs'.

  * 'flt_foreach_conf(px, side, fct, data)' iterates over the filter
    configurations of the proxy, in the evaluation order of the given side
    (FLT_SIDE_GLOBAL, FLT_SIDE_REQ or FLT_SIDE_RES). The '<fct>' callback is
    called with each 'struct flt_conf *' and must return 0 to continue. In
    legacy mode, the declaration order is used.

  * 'flt_find_conf(px, flt_id, side, match, data)' returns the first filter
    configuration matching the id '<flt_id>' (any id if NULL) and the optional
    '<match>' callback. This is the recommended way to find the filter
    configuration referenced by a rule.

  * 'flt_find_instance(px, cls, id)' returns the first filter instance of the
    class '<cls>' matching the id (any id if NULL), or NULL. It may be used
    when the filter instance itself is needed, but not to keep a pointer to it.

The match callback passed to flt_find_conf() typically compares the filter
configuration ('fconf->conf') with the reference stored in the rule. Since the
helpers select the right source according to the proxy mode, a filter
supporting both modes must not scan 'px->filter_configs' directly.


4. PORTING A LEGACY FILTER
---------------------------

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).
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.

4.1. WHAT CHANGED

The following points summarize the changes introduced by the filter instance
mode, from a filter developer point of view :

  * Filters are now described by a filter class. A class is registered
    globally and placed on the request and/or the response side relative to
    the other classes. Its name is the one used with "filter-config". It
    defines how its instances are parsed and whether several instances are
    allowed in the same proxy (see section 3.2).

  * The filter configurations of a proxy are no longer solely declared by
    the "filter" directive. In the new mode, each "filter-config" directive
    declares a filter instance of a class, with an optional id, and filter
    instances may also be inherited from a defaults section or declared
    implicitly by another directive (see section 2.2). A proxy explicitly
    uses one mode or the other, never both.

  * The core allocates the 'struct flt_conf' of a filter instance and calls
    the class parse callback to finalize it, after the whole configuration
    was parsed. In legacy mode, the core also allocates the 'struct flt_conf'
    and calls the keyword parser, but while the "filter" line is read.

  * 'struct flt_conf' has new fields: 'id', the filter configuration
    identifier, and 'by_req'/'by_res', internal links used to build the
    evaluation orders. The 'list' link remains internal too.

  * An explicitly declared filter instance is disabled by default. It must
    be enabled with the "enabled" token or with "filter-enable" to be
    instantiated on streams. Implicit instances are enabled and inherited
    instances keep the state they have in the defaults section (see section
    2.2).

  * 'flt_ops.check', 'init' and 'deinit' are called once per filter instance
    in the new mode, for all the instances of the proxy, including the
    disabled ones. In legacy mode, they are called once per "filter" line.
    The enabled state only controls whether stream filter instances are
    created.

  * New helpers are available: flt_parse_instance_legacy() and
    flt_add_implicit_instance() during the parsing phase, flt_has_filter(),
    flt_foreach_conf(), flt_find_conf() and flt_find_instance() during the
    check phase (see sections 3.2 and 3.7).

  * The filter classes and the filter instances of a proxy may be inspected
    with 'haproxy -vv' and the "show filter classes" and "show filter
    instance(s)" CLI commands (see section 2.2).

  * The filter instances inherited from a defaults section are copied into
    every proxy using it and are finalized for each of them. This also
    happens at runtime for the backends created with the "add backend" CLI
    command, where the per-thread initialization of the filters is deferred
    to each thread.

Nothing else changed: the 'flt_ops' callbacks, the stream filter instance
context and the data filtering constraints are the same in both modes. Only
the way the evaluation order is determined differs, as described in section
2. A filter that does not declare a class is simply never eligible to the
new mode.

4.2. MINIMAL CHANGES

An existing filter only has to declare its class and to provide a parse
callback delegating to its legacy keyword parser to be usable in both modes.
Its parsing code, its callback implementations and its implicit declarations
are left untouched.

1. Declare the class structure and write the parse callback. It must produce
   the same filter configuration as the legacy parser, so the simplest
   implementation reuses it through flt_parse_instance_legacy() :

    static struct filter_class flt_my_filter_cls;

    int my_filter_parse_instance(char **args, struct proxy *px,
                                 struct filter_instance *inst, char **err)
    {
        return flt_parse_instance_legacy(px, inst, err, "my_filter",
                                         parse_my_filter_cfg, NULL);
    }

   The helper rebuilds the argument vector expected by the legacy parser
   (the keyword followed by the instance arguments, NULL-terminated), calls
   it and checks that all the arguments were consumed. Beware that the
   legacy parser must not rely on parsing-time globals when it is called
   this way: 'curproxy', for instance, is not set anymore at this stage. If
   needed, save and restore such globals around the call, as done in the
   SPOE filter (see section 3.2.2). The class name may differ from the
   legacy keyword name: nothing forces them to be identical, but the
   "filter-config" directive uses the class name while the "filter"
   directive uses the keyword name.

2. Register the class and place it on each side it supports, relative to an
   already placed class. For a filter maintained out of the HAProxy tree,
   use the public API from an initcall, because the registration must happen
   after the built-in classes were initialized :

    static int my_filter_register_class(void)
    {
        /* Evaluate the filter before SPOE on the request path and after the
         * Lua filters on the response path. */
        if (filter_register_class(&flt_my_filter_cls, "my-filter",
                                  my_filter_parse_instance) < 0)
            return -1;
        if (filter_place_class(&flt_my_filter_cls, FLT_SIDE_REQ,
                               "spoe", FLT_POS_BEFORE) < 0)
            return -1;
        if (filter_place_class(&flt_my_filter_cls, FLT_SIDE_RES,
                               "lua", FLT_POS_AFTER) < 0)
            return -1;
        return 0;
    }
    INITCALL1(STG_INIT, my_filter_register_class);

   filter_register_class_full() registers the class and places it at the
   same position on both sides in a single call, which is enough for most
   filters.

   For a filter living in the HAProxy tree, the built-in classes are
   declared in src/filters.c and initialized in filter_init_classes(),
   where the default order of all internal classes is explicitly defined.
   Add the class there instead, with flt_init_class(), and insert it in the
   'filter_classes' list and in the 'req_filter_classes' or
   'res_filter_classes' lists at the desired positions. The parse callback
   must be declared in include/haproxy/filters.h.

3. That is all for the minimal conversion. The filter is now usable with
   "filter-config" in the new mode and still usable with "filter" in the
   legacy mode.

The 'trace' filter is the simplest example of a filter supporting both
modes. The HTTP compression and the decompression filters show how a feature
moved in a filter declares two classes, one per direction, and how implicit
instances are declared. The SPOE filter shows a legacy parser relying on
parsing-time globals, saved and restored by the instance parser.

4.3. FULLY USING THE FILTER INSTANCE MODE

The minimal conversion is enough to support both modes, but a filter may go
further to take advantage of the new mode. The points below are all optional
and only make sense if the filter can be instantiated several times, is
enabled by another directive, or is referenced by other directives.

  * Always declare an id for the filter configuration. The class parse
    callback should set 'inst->fconf->id' to identify its configuration.
    This is not the instance id: 'inst->id' is the id of the declaration,
    while 'fconf->id' is defined by the filter and used by flt_find_conf()
    to find a filter configuration. Both may be identical if that suits the
    filter. If dynamically allocated, 'fconf->id' must be freed by
    'flt_ops.deinit', as HAProxy never frees or duplicates it.

  * Allow several instances of the class. Set FLT_CLS_FL_MULTI in the class
    flags to allow several instances of the class in the same proxy, each
    one with an id. Without this flag, a second instance of the class in the
    same proxy is rejected, even with a different id. With the flag, an id
    is mandatory for each instance. Use flt_find_instance() to find an
    instance by class and id.

  * Declare an implicit instance when the filter is enabled by another
    directive. If the filter can be enabled by another directive, for
    instance by a "compression" option or by a "cache-store" rule, the
    directive parser must call flt_add_implicit_instance() instead of
    forcing the user to add a "filter" line. The instance is created enabled
    and flagged FLT_INST_F_IMPLICIT. An explicit "filter-config" instance
    with the same class and id then replaces it (see section 3.2.1).

  * Handle inherited instances. A filter instance declared in a defaults
    section is copied into every proxy inheriting from it, and its parse
    callback is called again for each proxy, with FLT_INST_F_INHERITED set.
    The callback must be idempotent and must not keep state from a previous
    call. Fields that depend on the proxy capabilities must be left unset if
    the final proxy does not support them: the instance is kept as metadata
    and the filter may decline its attachment to the streams of that proxy
    by returning 0 in 'flt_ops.attach'.

  * Reference filter instances from other directives. A directive that
    refers to a filter instance must not keep a pointer to the instance or
    to its configuration: the instances are finalized at the end of the
    configuration parsing and may be replaced by a local or an inherited
    declaration. The reference must be resolved from the check callback of
    the directive, with flt_find_conf() or flt_foreach_conf() (see section
    3.7).

4.4. PITFALLS AND DESIGN CONSIDERATIONS

  * The class parse callback is not a directive parser. It is called much
    later, after the whole configuration was parsed, once per instance, and
    possibly several times for the same declaration (defaults inheritance)
    and even at runtime (dynamic backends). Therefore :

      - it must not rely on the relative order of the directives;

      - it must not rely on parsing-time globals, 'curproxy' in particular,
        nor on data allocated while parsing, except the instance arguments
        and the instance attributes, which are provided to it;

      - it must be idempotent, because it may be called again for the same
        instance in a child proxy;

      - other directives cannot store a pointer to the instance: they must
        resolve it at the check stage.

  * Do not replace or release 'inst->fconf'. The core allocates it and
    passes it to the parse callback, in both modes. Only 'fconf->ops',
    'fconf->id', 'fconf->conf' and 'fconf->flags' may be set by the filter.
    The 'fconf->list', 'fconf->by_req' and 'fconf->by_res' links must not be
    touched.

  * 'fconf->ops' is mandatory in both modes and all the arguments of the
    instance must be consumed, otherwise the configuration is rejected.
    'fconf->conf' is owned by the filter and must be released from
    'flt_ops.deinit'.

  * An explicit filter instance is disabled by default. Forgetting the
    "enabled" token (or "filter-enable") means the filter is never
    instantiated on streams, while 'check', 'init', 'deinit' and the
    per-thread callbacks are still called for it. A filter must not use the
    enabled state to decide whether its configuration must be initialized or
    released, and must not expect a disabled instance to be attached to a
    stream.

  * 'flt_ops.attach' must return a positive value to attach the filter to
    the stream, 1 by convention, and 0 to ignore it for this stream. A
    negative value is an error. This differs from most of the other
    callbacks, which return 0 on success, and it is easy to get wrong:
    returning 0 for a successful attach silently excludes the filter from
    all the streams of the proxy.

  * Side effects on the proxy options must be idempotent and must not
    depend on the instance being declared locally: the parse callback is
    also called for implicit and inherited instances, and an explicit
    declaration replaces an implicit one. For implicit instances, the
    options the directive did not enable must not be re-enabled.

  * A class must be placed on at least one side, otherwise it is rejected
    at startup. The placement defines the default evaluation order on each
    side, but the user may change it per proxy with "filter-sequence". A
    filter must not assume a fixed position relative to the other filters
    unless it owns the placement of the classes involved and knows that the
    sequences cannot change it. The global order, which follows the class
    registration order, only drives the side-independent operations
    (configuration checks, initialization and destruction).

  * A class must be registered after the built-in classes were initialized,
    from a STG_INIT initcall or a later stage, otherwise the registration
    fails. For a filter inside the HAProxy tree, the class is registered in
    filter_init_classes() to explicitly define its default order with the
    other internal classes.

  * The instance id is optional for a class that does not set
    FLT_CLS_FL_MULTI: the parse callback may receive a NULL 'inst->id'. A
    class allowing several instances must require an id and use it to
    distinguish its instances. Note that 'inst->id' is not 'fconf->id' and
    that both may be used for different purposes (see section 4.3).

  * The legacy mode still applies to the filters that only register a
    keyword: a proxy using the "filter" directive knows nothing about
    classes and instances. In this mode, ids do not exist and the filter is
    responsible for rejecting duplicated declarations, if needed. Explicit
    legacy "filter" and "filter-*" directives cannot be used in the same
    proxy, but implicit filters may coexist with either mode. In legacy
    mode, an implicit declaration is only effective when the filter is
    alone.

  * A filter configuration and a filter instance are never shared between
    proxies, streams or threads: inherited instances are duplicated, and the
    per-thread callbacks are executed in the context of each thread. Never
    store per-stream data in the filter configuration or in the filter
    instance, use 'filter.ctx' for that purpose.

  * The filters of a proxy created at runtime (with "add backend") are
    finalized on the thread performing the creation, and their per-thread
    initialization is deferred to every thread. A filter that initializes
    thread-local resources, the Lua states for instance, must implement the
    per-thread callbacks for these resources to be initialized on each
    thread, including the threads that did not create the proxy (see section
    3.3.1).

  * Finally, the mode is selected by the configuration, not by the filter:
    the same filter may be used in legacy mode in one proxy and in the new
    mode in another. The output of 'haproxy -vv' and the "show filter
    classes" and "show filter instances" commands help to check what is
    really used by a proxy.


5. FAQ
-----

5.1. Which mode should a new filter support?

    A new filter may support only the new mode. The legacy "filter" directive
    has to be supported only to remain compatible with existing configurations
    relying on it. That said, if the filter results from the conversion of an
    existing feature, supporting both modes is recommended. Both modes may
    share the same parsing code through flt_parse_instance_legacy() (see section
    3.2.2).

    A filter is usable in the new mode as soon as it registers a filter class
    (section 3.2); it is usable in the legacy mode as soon as it registers a
    "filter" keyword (section 3.2.2). Nothing forces a filter to support both,
    but a filter supporting only the new mode cannot be enabled by another
    directive in legacy mode.

5.2. Why is my filter instance ignored?

    By default, a filter instance declared with "filter-config" is disabled.
    The "enabled" token or a "filter-enable" directive is required to activate
    it. The state of a filter instance is reported by "show filter instance"
    (section 2.2).

5.3. When is the configuration of my filter parsed?

    In the new mode, the class parse callback is called after the whole
    configuration was parsed, once per filter instance, and after the filter
    instances of the defaults section were inherited. It is not called while
    the "filter-config" line is read. Directives that reference the filter must
    therefore resolve it at the check stage with flt_find_conf() (section 3.7).
    On the contrary, in legacy mode the parsing function is called while the
    "filter" line is read.

5.4. How can I declare several filter instances of the same class in a proxy?

    The class must set FLT_CLS_FL_MULTI, in which case each filter instance must
    have an id. Otherwise a single filter instance per proxy is allowed, and a
    second one - explicit, inherited or implicit - is rejected as a duplicate.

5.5. How is the evaluation order determined?

    In the new mode, by the class placement on the request and response sides
    (section 3.2) and, possibly, by the "filter-sequence" directive on a per
    proxy basis (section 2.2). The global order follows the class registration
    order. A filter must not assume a fixed order relative to other filters
    unless it owns the corresponding placements or sequences.

    In legacy mode, the declaration order of the "filter" directives is used.

5.6. Can a filter instance be shared between proxies?

    No. The filter instances of a defaults section are copied into the
    inheriting proxies, each one with its own filter configuration. A filter
    configuration, and a fortiori a stream filter instance context, must never
    be shared between proxies, streams or threads unless the filter explicitly
    supports it.

5.7. How to keep an unported legacy filter working?

    A filter relying on an implicit declaration only and not registering a
    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.

    In this case, the "filter-api downgrade-to-legacy" global option restores
    the behavior of HAProxy 3.4: the proxies without explicit filter
    declaration use the legacy mode, so the implicit declarations keep working.
    The proxies with an explicit declaration keep the mode they selected, so a
    configuration may use both modes simultaneously, on different proxies.
    Of course, the right fix remains to port the filter (see section 4).

    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
    be silently ignored if the filter API is downgraded.
