CONTRIB: move contrib/opentracing to addons/ot

This one is the last optional module to build with haproxy, so let's move
it to addons/. It was renamed to "ot" as it was the only one whose USE_*
option did not match the directory name, now this is consistent.

Few changes were required, only the Makefile, and doc were adjusted, as
the directory was already self-contained and relocatable.
diff --git a/addons/ot/AUTHORS b/addons/ot/AUTHORS
new file mode 100644
index 0000000..92b2831
--- /dev/null
+++ b/addons/ot/AUTHORS
@@ -0,0 +1 @@
+Miroslav Zagorac <mzagorac@haproxy.com>
diff --git a/addons/ot/MAINTAINERS b/addons/ot/MAINTAINERS
new file mode 100644
index 0000000..92b2831
--- /dev/null
+++ b/addons/ot/MAINTAINERS
@@ -0,0 +1 @@
+Miroslav Zagorac <mzagorac@haproxy.com>
diff --git a/addons/ot/Makefile b/addons/ot/Makefile
new file mode 100644
index 0000000..9d9b1bf
--- /dev/null
+++ b/addons/ot/Makefile
@@ -0,0 +1,61 @@
+# USE_OT     : enable the OpenTracing filter
+# OT_DEBUG   : compile the OpenTracing filter in debug mode
+# OT_INC     : force the include path to libopentracing-c-wrapper
+# OT_LIB     : force the lib path to libopentracing-c-wrapper
+# OT_RUNPATH : add libopentracing-c-wrapper RUNPATH to haproxy executable
+
+OT_DEFINE    =
+OT_CFLAGS    =
+OT_LDFLAGS   =
+OT_DEBUG_EXT =
+OTC_WRAPPER  = opentracing-c-wrapper
+
+ifneq ($(OT_DEBUG),)
+OT_DEBUG_EXT = _dbg
+OT_DEFINE    = -DDEBUG_OT
+endif
+
+ifeq ($(OT_INC),)
+OT_CFLAGS = $(shell pkg-config --silence-errors --cflags $(OTC_WRAPPER)$(OT_DEBUG_EXT))
+else
+ifneq ($(wildcard $(OT_INC)/$(OTC_WRAPPER)/.*),)
+OT_CFLAGS = -I$(OT_INC) $(if $(OT_DEBUG),-DOTC_DBG_MEM)
+endif
+endif
+
+ifeq ($(OT_CFLAGS),)
+$(error OpenTracing C wrapper : can't find headers)
+endif
+
+ifeq ($(OT_LIB),)
+OT_LDFLAGS = $(shell pkg-config --silence-errors --libs $(OTC_WRAPPER)$(OT_DEBUG_EXT))
+else
+ifneq ($(wildcard $(OT_LIB)/lib$(OTC_WRAPPER).*),)
+OT_LDFLAGS = -L$(OT_LIB) -l$(OTC_WRAPPER)$(OT_DEBUG_EXT)
+ifneq ($(OT_RUNPATH),)
+OT_LDFLAGS += -Wl,--rpath,$(OT_LIB)
+endif
+endif
+endif
+
+ifeq ($(OT_LDFLAGS),)
+$(error OpenTracing C wrapper : can't find library)
+endif
+
+OPTIONS_OBJS += \
+	addons/ot/src/cli.o         \
+	addons/ot/src/conf.o        \
+	addons/ot/src/event.o       \
+	addons/ot/src/filter.o      \
+	addons/ot/src/group.o       \
+	addons/ot/src/http.o        \
+	addons/ot/src/opentracing.o \
+	addons/ot/src/parser.o      \
+	addons/ot/src/pool.o        \
+	addons/ot/src/scope.o       \
+	addons/ot/src/util.o        \
+	addons/ot/src/vars.o
+
+OPTIONS_CFLAGS  += $(OT_CFLAGS) -Iaddons/ot/include
+OPTIONS_LDFLAGS += $(OT_LDFLAGS)
+OPTIONS_CFLAGS  += $(OT_DEFINE)
diff --git a/addons/ot/README b/addons/ot/README
new file mode 100644
index 0000000..a08f471
--- /dev/null
+++ b/addons/ot/README
@@ -0,0 +1,794 @@
+                   -----------------------------------------
+                      The HAProxy OpenTracing filter (OT)
+                                  Version 1.0
+                          ( Last update: 2020-12-10 )
+                   -----------------------------------------
+                           Author : Miroslav Zagorac
+                     Contact : mzagorac at haproxy dot com
+
+
+SUMMARY
+--------
+
+  0.    Terms
+  1.    Introduction
+  2.    Build instructions
+  3.    Basic concepts in OpenTracing
+  4.    OT configuration
+  4.1.    OT scope
+  4.2.    "ot-tracer" section
+  4.3.    "ot-scope" section
+  4.4.    "ot-group" section
+  5.    Examples
+  5.1     Benchmarking results
+  6.    OT CLI
+  7.    Known bugs and limitations
+
+
+0. Terms
+---------
+
+* OT: The HAProxy OpenTracing filter
+
+  OT is the HAProxy filter that allows you to send data to distributed
+  tracing systems via the OpenTracing API.
+
+
+1. Introduction
+----------------
+
+Nowadays there is a growing need to divide a process into microservices and
+there is a problem of monitoring the work of the same process.  One way to
+solve this problem is to use distributed tracing service in a central location,
+the so-called tracer.
+
+OT is a feature introduced in HAProxy 2.4.  This filter enables communication
+via the OpenTracing API with OpenTracing compatible servers (tracers).
+Currently, tracers that support this API include Datadog, Jaeger, LightStep
+and Zipkin.
+
+The OT filter was primarily tested with the Jaeger tracer, while configurations
+for both Datadog and Zipkin tracers were also set in the test directory.
+
+The OT filter is a standard HAProxy filter, so what applies to others also
+applies to this one (of course, by that I mean what is described in the
+documentation, more precisely in the doc/internals/filters.txt file).
+
+The OT filter activation is done explicitly by specifying it in the HAProxy
+configuration.  If this is not done, the OT filter in no way participates
+in the work of HAProxy.
+
+As for the impact on HAProxy speed, this is documented with several tests
+located in the test directory, and the result is found in the README-speed-*
+files.  In short, the speed of operation depends on the way it is used and
+the complexity of the configuration, from an almost immeasurable impact to
+a significant deceleration (5x and more).  I think that in some normal use
+the speed of HAProxy with the filter on will be quite satisfactory with a
+slowdown of less than 4% (provided that no more than 10% of requests are
+sent to the tracer, which is determined by the keyword 'rate-limit').
+
+The OT filter allows intensive use of ACLs, which can be defined anywhere in
+the configuration.  Thus, it is possible to use the filter only for those
+connections that are of interest to us.
+
+
+2. Build instructions
+----------------------
+
+OT is the HAProxy filter and as such is compiled together with HAProxy.
+
+To communicate with some OpenTracing compatible tracer, the OT filter uses the
+OpenTracing C Wrapper library (which again uses the OpenTracing CPP library).
+This means that we must have both libraries installed on the system on which
+we want to compile or use HAProxy.
+
+Instructions for compiling and installing both required libraries can be
+found at https://github.com/haproxytech/opentracing-c-wrapper .
+
+Also, to use the OT filter when running HAProxy we need to have an OpenTracing
+plugin for the tracer we want to use.  We will return to this later, in
+section 5.
+
+The OT filter can be more easily compiled using the pkg-config tool, if we
+have the OpenTracing C Wrapper library installed so that it contains pkg-config
+files (which have the .pc extension).  If the pkg-config tool cannot be used,
+then the path to the directory where the include files and libraries are
+located can be explicitly specified.
+
+Below are examples of the two ways to compile HAProxy with the OT filter, the
+first using the pkg-congfig tool and the second explicitly specifying the path
+to the OpenTracing C Wrapper include and library.
+
+Note: prompt '%' indicates that the command is executed under a unprivileged
+      user, while prompt '#' indicates that the command is executed under the
+      root user.
+
+Example of compiling HAProxy using the pkg-congfig tool (assuming the
+OpenTracing C Wrapper library is installed in the /opt directory):
+
+  % PKG_CONFIG_PATH=/opt/lib/pkgconfig make USE_OT=1 TARGET=linux-glibc
+
+The OT filter can also be compiled in debug mode as follows:
+
+  % PKG_CONFIG_PATH=/opt/lib/pkgconfig make USE_OT=1 OT_DEBUG=1 TARGET=linux-glibc
+
+HAProxy compilation example explicitly specifying path to the OpenTracing C
+Wrapper include and library:
+
+  % make USE_OT=1 OT_INC=/opt/include OT_LIB=/opt/lib TARGET=linux-glibc
+
+In case we want to use debug mode, then it looks like this:
+
+  % make USE_OT=1 OT_DEBUG=1 OT_INC=/opt/include OT_LIB=/opt/lib TARGET=linux-glibc
+
+If the library we want to use is not installed on a unix system, then a locally
+installed library can be used (say, which is compiled and installed in the user
+home directory).  In this case instead of /opt/include and /opt/lib the
+equivalent paths to the local installation should be specified.  Of course,
+in that case the pkg-config tool can also be used if we have a complete
+installation (with .pc files).
+
+last but not least, if the pkg-config tool is not used when compiling, then
+HAProxy executable may not be able to find the OpenTracing C Wrapper library
+at startup.  This can be solved in several ways, for example using the
+LD_LIBRARY_PATH environment variable which should be set to the path where the
+library is located before starting the HAProxy.
+
+  % LD_LIBRARY_PATH=/opt/lib /path-to/haproxy ...
+
+Another way is to add RUNPATH to HAProxy executable that contains the path to
+the library in question.
+
+  % make USE_OT=1 OT_RUNPATH=1 OT_INC=/opt/include OT_LIB=/opt/lib TARGET=linux-glibc
+
+After HAProxy is compiled, we can check if the OT filter is enabled:
+
+  % ./haproxy -vv | grep opentracing
+  --- command output ----------
+          [  OT] opentracing
+  --- command output ----------
+
+
+3. Basic concepts in OpenTracing
+---------------------------------
+
+Basic concepts of OpenTracing can be read on the OpenTracing documentation
+website https://opentracing.io/docs/overview/.
+
+Here we will list only the most important elements of distributed tracing and
+these are 'trace', 'span' and 'span context'.  Trace is a description of the
+complete transaction we want to record in the tracing system.  A span is an
+operation that represents a unit of work that is recorded in a tracing system.
+Span context is a group of information related to a particular span that is
+passed on to the system (from service to service).  Using this context, we can
+add new spans to already open trace (or supplement data in already open spans).
+
+An individual span may contain one or more tags, logs and baggage items.
+The tag is a key-value element that is valid for the entire span.  Log is a
+key-value element that allows you to write some data at a certain time, it
+can be used for debugging.  A baggage item is a key-value data pair that can
+be used for the duration of an entire trace, from the moment it is added to
+the span.
+
+
+4. OT configuration
+--------------------
+
+In order for the OT filter to be used, it must be included in the HAProxy
+configuration, in the proxy section (frontend / listen / backend):
+
+   frontend ot-test
+     ...
+     filter opentracing [id <id>] config <file>
+     ...
+
+If no filter id is specified, 'ot-filter' is used as default.  The 'config'
+parameter must be specified and it contains the path of the file used to
+configure the OT filter.
+
+
+4.1 OT scope
+-------------
+
+If the filter id is defined for the OT filter, then the OT scope with
+the same name should be defined in the configuration file.  In the same
+configuration file we can have several defined OT scopes.
+
+Each OT scope must have a defined (only one) "ot-tracer" section that is
+used to configure the operation of the OT filter and define the used groups
+and scopes.
+
+OT scope starts with the id of the filter specified in square brackets and
+ends with the end of the file or when a new OT scope is defined.
+
+For example, this defines two OT scopes in the same configuration file:
+  [my-first-ot-filter]
+    ot-tracer tracer1
+    ...
+    ot-group group1
+    ...
+    ot-scope scope1
+    ...
+
+  [my-second-ot-filter]
+    ...
+
+
+4.2. "ot-tracer" section
+-------------------------
+
+Only one "ot-tracer" section must be defined for each OT scope.
+
+There are several keywords that must be defined for the OT filter to work.
+These are 'config' which defines the configuration file for the OpenTracing
+API, and 'plugin' which defines the OpenTracing plugin used.
+
+Through optional keywords can be defined ACLs, logging, rate limit, and groups
+and scopes that define the tracing model.
+
+
+ot-tracer <name>
+  A new OT with the name <name> is created.
+
+  Arguments :
+    name - the name of the tracer section
+
+
+  The following keywords are supported in this section:
+    - mandatory keywords:
+      - config
+      - plugin
+
+    - optional keywords:
+      - acl
+      - debug-level
+      - groups
+      - [no] log
+      - [no] option disabled
+      - [no] option dontlog-normal
+      - [no] option hard-errors
+      - rate-limit
+      - scopes
+
+
+acl <aclname> <criterion> [flags] [operator] <value> ...
+  Declare or complete an access list.
+
+  To configure and use the ACL, see section 7 of the HAProxy Configuration
+  Manual.
+
+
+config <file>
+  'config' is one of the two mandatory keywords associated with the OT tracer
+  configuration.  This keyword sets the path of the configuration file for the
+  OpenTracing tracer plugin.  To set the contents of this configuration file,
+  it is best to look at the documentation related to the OpenTracing tracer we
+  want to use.
+
+  Arguments :
+    file - the path of the configuration file
+
+
+debug-level <value>
+  This keyword sets the value of the debug level related to the display of
+  debug messages in the OT filter.  The 'debug-level' value is binary, ie
+  a single value bit enables or disables the display of the corresponding
+  debug message that uses that bit.  The default value is set via the
+  FLT_OT_DEBUG_LEVEL macro in the include/config.h file.  Debug level value
+  is used only if the OT filter is compiled with the debug mode enabled,
+  otherwise it is ignored.
+
+  Arguments :
+    value - binary value ranging from 0 to 255 (8 bits)
+
+
+groups <name> ...
+  A list of "ot-group" groups used for the currently defined tracer is declared.
+  Several groups can be specified in one line.
+
+  Arguments :
+    name - the name of the OT group
+
+
+log global
+log <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]]
+no log
+  Enable per-instance logging of events and traffic.
+
+  To configure and use the logging system, see section 4.2 of the HAProxy
+  Configuration Manual.
+
+
+option disabled
+no option disabled
+  Keyword which turns the operation of the OT filter on or off.  By default
+  the filter is on.
+
+
+option dontlog-normal
+no option dontlog-normal
+  Enable or disable logging of normal, successful processing.  By default,
+  this option is disabled.  For this option to be considered, logging must
+  be turned on.
+
+  See also: 'log' keyword description.
+
+
+option hard-errors
+no option hard-errors
+  During the operation of the filter, some errors may occur, caused by
+  incorrect configuration of the tracer or some error related to the operation
+  of HAProxy.  By default, such an error will not interrupt the filter
+  operation for the stream in which the error occurred.  If the 'hard-error'
+  option is enabled, the operation error prohibits all further processing of
+  events and groups in the stream in which the error occurred.
+
+
+plugin <file>
+  'plugin' is one of the two mandatory keywords associated with the OT tracer
+  configuration.  This keyword sets the path of the OpenTracing tracer plugin.
+
+  Arguments :
+    file - the name of the plugin used
+
+
+rate-limit <value>
+  This option allows limiting the use of the OT filter, ie it can be influenced
+  whether the OT filter is activated for a stream or not.  Determining whether
+  or not a filter is activated depends on the value of this option that is
+  compared to a randomly selected value when attaching the filter to the stream.
+  By default, the value of this option is set to 100.0, ie the OT filter is
+  activated for each stream.
+
+  Arguments :
+    value - floating point value ranging from 0.0 to 100.0
+
+
+scopes <name> ...
+  This keyword declares a list of "ot-scope" definitions used for the currently
+  defined tracer.  Multiple scopes can be specified in the same line.
+
+  Arguments :
+    name - the name of the OT scope
+
+
+4.3. "ot-scope" section
+------------------------
+
+Stream processing begins with filter attachment, then continues with the
+processing of a number of defined events and groups, and ends with filter
+detachment.  The "ot-scope" section is used to define actions related to
+individual events.  However, this section may be part of a group, so the
+event does not have to be part of the definition.
+
+
+ot-scope <name>
+  Creates a new OT scope definition named <name>.
+
+  Arguments :
+    name - the name of the OT scope
+
+
+  The following keywords are supported in this section:
+    - acl
+    - baggage
+    - event
+    - extract
+    - finish
+    - inject
+    - log
+    - span
+    - tag
+
+
+acl <aclname> <criterion> [flags] [operator] <value> ...
+  Declare or complete an access list.
+
+  To configure and use the ACL, see section 7 of the HAProxy Configuration
+  Manual.
+
+
+baggage <name> <sample> ...
+  Baggage items allow the propagation of data between spans, ie allow the
+  assignment of metadata that is propagated to future children spans.
+  This data is formatted in the style of key-value pairs and is part of
+  the context that can be transferred between processes that are part of
+  a server architecture.
+
+  This kewyord allows setting the baggage for the currently active span.  The
+  data type is always a string, ie any sample type is converted to a string.
+  The exception is a binary value that is not supported by the OT filter.
+
+  See the 'tag' keyword description for the data type conversion table.
+
+  Arguments :
+    name   - key part of a data pair
+    sample - sample expression (value part of a data pair), at least one
+             sample must be present
+
+
+event <name> [{ if | unless } <condition>]
+  Set the event that triggers the 'ot-scope' to which it is assigned.
+  Optionally, it can be followed by an ACL-based condition, in which case it
+  will only be evaluated if the condition is true.
+
+  ACL-based conditions are executed in the context of a stream that processes
+  the client and server connections.  To configure and use the ACL, see
+  section 7 of the HAProxy Configuration Manual.
+
+  Arguments :
+    name      - the event name
+    condition - a standard ACL-based condition
+
+  Supported events are (the table gives the names of the events in the OT
+  filter and the corresponding equivalent in the SPOE filter):
+
+    -------------------------------------|------------------------------
+      the OT filter                      |  the SPOE filter
+    -------------------------------------|------------------------------
+      on-client-session-start            |  on-client-session
+      on-frontend-tcp-request            |  on-frontend-tcp-request
+      on-http-wait-request               |  -
+      on-http-body-request               |  -
+      on-frontend-http-request           |  on-frontend-http-request
+      on-switching-rules-request         |  -
+      on-backend-tcp-request             |  on-backend-tcp-request
+      on-backend-http-request            |  on-backend-http-request
+      on-process-server-rules-request    |  -
+      on-http-process-request            |  -
+      on-tcp-rdp-cookie-request          |  -
+      on-process-sticking-rules-request  |  -
+      on-client-session-end              |  -
+      on-server-unavailable              |  -
+    -------------------------------------|------------------------------
+      on-server-session-start            |  on-server-session
+      on-tcp-response                    |  on-tcp-response
+      on-http-wait-response              |  -
+      on-process-store-rules-response    |  -
+      on-http-response                   |  on-http-response
+      on-server-session-end              |  -
+    -------------------------------------|------------------------------
+
+
+extract <name-prefix> [use-vars | use-headers]
+  For a more detailed description of the propagation process of the span
+  context, see the description of the keyword 'inject'.  Only the process
+  of extracting data from the carrier is described here.
+
+  Arguments :
+    name-prefix - data name prefix (ie key element prefix)
+    use-vars    - data is extracted from HAProxy variables
+    use-headers - data is extracted from the HTTP header
+
+
+  Below is an example of using HAProxy variables to transfer span context data:
+
+  --- test/ctx/ot.cfg --------------------------------------------------------
+      ...
+      ot-scope client_session_start_2
+          extract "ot_ctx_1" use-vars
+          span "Client session" child-of "ot_ctx_1"
+      ...
+  ----------------------------------------------------------------------------
+
+
+finish <name> ...
+  Closing a particular span or span context.  Instead of the name of the span,
+  there are several specially predefined names with which we can finish certain
+  groups of spans.  So it can be used as the name '*req*' for all open spans
+  related to the request channel, '*res*' for all open spans related to the
+  response channel and '*' for all open spans regardless of which channel they
+  are related to.  Several spans and/or span contexts can be specified in one
+  line.
+
+  Arguments :
+    name - the name of the span or context context
+
+
+inject <name-prefix> [use-vars] [use-headers]
+  In OpenTracing, the transfer of data related to the tracing process between
+  microservices that are part of a larger service is done through the
+  propagation of the span context.  The basic operations that allow us to
+  access and transfer this data are 'inject' and 'extract'.
+
+  'inject' allows us to extract span context so that the obtained data can
+  be forwarded to another process (microservice) via the selected carrier.
+  'inject' in the name actually means inject data into carrier.  Carrier is
+  an interface here (ie a data structure) that allows us to transfer tracing
+  state from one process to another.
+
+  Data transfer can take place via one of two selected storage methods, the
+  first is by adding data to the HTTP header and the second is by using HAProxy
+  variables.  Only data transfer via HTTP header can be used to transfer data
+  to another process (ie microservice).  All data is organized in the form of
+  key-value data pairs.
+
+  No matter which data transfer method you use, we need to specify a prefix
+  for the key element.  All alphanumerics (lowercase only) and underline
+  character can be used to construct the data name prefix.  Uppercase letters
+  can actually be used, but they will be converted to lowercase when creating
+  the prefix.
+
+  Arguments :
+    name-prefix - data name prefix (ie key element prefix)
+    use-vars    - HAProxy variables are used to store and transfer data
+    use-headers - HTTP headers are used to store and transfer data
+
+
+  Below is an example of using HTTP headers and variables, and how this is
+  reflected in the internal data of the HAProxy process.
+
+  --- test/ctx/ot.cfg --------------------------------------------------------
+      ...
+      ot-scope client_session_start_1
+          span "HAProxy session" root
+              inject "ot_ctx_1" use-headers use-vars
+      ...
+  ----------------------------------------------------------------------------
+
+    - generated HAProxy variable (key -> value):
+      txn.ot_ctx_1.uberDtraceDid -> 8f1a05a3518d2283:8f1a05a3518d2283:0:1
+
+    - generated HTTP header (key: value):
+      ot_ctx_1-uber-trace-id: 8f1a05a3518d2283:8f1a05a3518d2283:0:1
+
+  Because HAProxy does not allow the '-' character in the variable name (which
+  is automatically generated by the OpenTracing API and on which we have no
+  influence), it is converted to the letter 'D'.  We can see that there is no
+  such conversion in the name of the HTTP header because the '-' sign is allowed
+  there.  Due to this conversion, initially all uppercase letters are converted
+  to lowercase because otherwise we would not be able to distinguish whether
+  the disputed sign '-' is used or not.
+
+  Thus created HTTP headers and variables are deleted when executing the
+  'finish' keyword or when detaching the stream from the filter.
+
+
+log <name> <sample> ...
+  This kewyord allows setting the log for the currently active span.  The
+  data type is always a string, ie any sample type is converted to a string.
+  The exception is a binary value that is not supported by the OT filter.
+
+  See the 'tag' keyword description for the data type conversion table.
+
+  Arguments :
+    name   - key part of a data pair
+    sample - sample expression (value part of a data pair), at least one
+             sample must be present
+
+
+span <name> [<reference>]
+  Creating a new span (or referencing an already opened one).  If a new span
+  is created, it can be a child of the referenced span, follow from the
+  referenced span, or be root 'span'.  In case we did not specify a reference
+  to the previously created span, the new span will become the root span.
+  We need to pay attention to the fact that in one trace there can be only
+  one root span.  In case we have specified a non-existent span as a reference,
+  a new span will not be created.
+
+  Arguments :
+    name      - the name of the span being created or referenced (operation
+                name)
+    reference - span or span context to which the created span is referenced
+
+
+tag <name> <sample> ...
+  This kewyord allows setting a tag for the currently active span.  The first
+  argument is the name of the tag (tag ID) and the second its value.  A value
+  can consist of one or more data.  If the value is only one data, then the
+  type of that data depends on the type of the HAProxy sample.  If the value
+  contains more data, then the data type is string.  The data conversion table
+  is below:
+
+   HAProxy sample data type | the OpenTracing data type
+  --------------------------+---------------------------
+            NULL            |        NULL
+            BOOL            |        BOOL
+            INT32           |        INT64
+            UINT32          |        UINT64
+            INT64           |        INT64
+            UINT64          |        UINT64
+            IPV4            |        STRING
+            IPV6            |        STRING
+            STRING          |        STRING
+            BINARY          |        UNSUPPORTED
+  --------------------------+---------------------------
+
+  Arguments :
+    name   - key part of a data pair
+    sample - sample expression (value part of a data pair), at least one
+             sample must be present
+
+
+4.4. "ot-group" section
+------------------------
+
+This section allows us to define a group of OT scopes, that is not activated
+via an event but is triggered from TCP or HTTP rules.  More precisely, these
+are the following rules: 'tcp-request', 'tcp-response', 'http-request',
+'http-response' and 'http-after-response'.  These rules can be defined in the
+HAProxy configuration file.
+
+
+ot-group <name>
+  Creates a new OT group definition named <name>.
+
+  Arguments :
+    name - the name of the OT group
+
+
+  The following keywords are supported in this section:
+    - scopes
+
+
+scopes <name> ...
+  'ot-scope' sections that are part of the specified group are defined.  If
+  the mentioned 'ot-scope' sections are used only in some OT group, they do
+  not have to have defined events.  Several 'ot-scope' sections can be
+  specified in one line.
+
+  Arguments :
+    name - the name of the 'ot-scope' section
+
+
+5. Examples
+------------
+
+Several examples of the OT filter configuration can be found in the test
+directory.  A brief description of the prepared configurations follows:
+
+cmp   - the configuration very similar to that of the spoa-opentracing project.
+        It was made to compare the speed of the OT filter with the
+        implementation of distributed tracing via spoa-opentracing application.
+
+sa    - the configuration in which all possible events are used.
+
+ctx   - the configuration is very similar to the previous one, with the only
+        difference that the spans are opened using the span context as a span
+        reference.
+
+fe be - a slightly more complicated example of the OT filter configuration
+        that uses two cascaded HAProxy services.  The span context between
+        HAProxy processes is transmitted via the HTTP header.
+
+empty - the empty configuration in which the OT filter is initialized but
+        no event is triggered.  It is not very usable, except to check the
+        behavior of the OT filter in the case of a similar configuration.
+
+
+In order to be able to collect data (and view results via the web interface)
+we need to install some of the supported tracers.  We will use the Jaeger
+tracer as an example.  Installation instructions can be found on the website
+https://www.jaegertracing.io/download/.  For the impatient, here we will list
+how the image to test the operation of the tracer system can be installed
+without much reading of the documentation.
+
+  # docker pull jaegertracing/all-in-one:latest
+  # docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
+    -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 \
+    -p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:latest
+
+The last command will also initialize and run the Jaeger container.  If we
+want to use that container later, it can be started and stopped in the classic
+way, using the 'docker container start/stop' commands.
+
+
+In order to be able to use any of the configurations from the test directory,
+we must also have a tracer plugin in that directory (all examples use the
+Jaeger tracer plugin).  The simplest way is to download the tracer plugin
+using the already prepared shell script get-opentracing-plugins.sh.
+The script accepts one argument, the directory in which the download is made.
+If run without an argument, the script downloads all plugins to the current
+directory.
+
+  % ./get-opentracing-plugins.sh
+
+After that, we can run one of the pre-configured configurations using the
+provided script run-xxx.sh (where xxx is the name of the configuration being
+tested).  For example:
+
+  % ./run-sa.sh
+
+The script will create a new log file each time it is run (because part of the
+log file name is the start time of the script).
+
+Eh, someone will surely notice that all test configurations use the Jaeger
+tracing plugin that cannot be downloaded using the get-opentracing-plugins.sh
+script.  Unfortunately, the latest precompiled version that can be downloaded
+is 0.4.2, for newer ones only the source code can be found.  Version 0.4.2 has
+a bug that can cause the operation of the OT filter to get stuck, so it is
+better not to use this version.  Here is the procedure by which we can compile
+a newer version of the plugin (in our example it is 0.5.0).
+
+Important note: the GCC version must be at least 4.9 or later.
+
+  % wget https://github.com/jaegertracing/jaeger-client-cpp/archive/v0.5.0.tar.gz
+  % tar xf v0.5.0.tar.gz
+  % cd jaeger-client-cpp-0.5.0
+  % mkdir build
+  % cd build
+  % cmake -DCMAKE_INSTALL_PREFIX=/opt -DJAEGERTRACING_PLUGIN=ON -DHUNTER_CONFIGURATION_TYPES=Release -DHUNTER_BUILD_SHARED_LIBS=OFF ..
+  % make
+
+After the plugin is compiled, it will be in the current directory.  The name
+of the plugin is libjaegertracing_plugin.so.
+
+
+5.1. Benchmarking results
+--------------------------
+
+To check the operation of the OT filter, several different test configurations
+have been made which are located in the test directory.  The test results of
+the same configurations (with the names README-speed-xxx, where xxx is the name
+of the configuration being tested) are also in the directory of the same name.
+
+All tests were performed on the same debian 9.13 system, CPU i7-4770, 32 GB RAM.
+For the purpose of testing, the thttpd web server on port 8000 was used.
+Testing was done with the wrk utility running via run-xxx.sh scripts; that is,
+via the test-speed.sh script that is run as follows:
+
+  % ./test-speed.sh all
+
+The above mentioned thttpd web server is run from that script and it should be
+noted that we need to have the same installed on the system (or change the path
+to the thttpd server in that script if it is installed elsewhere).
+
+Each test is performed several times over a period of 5 minutes per individual
+test.  The only difference when running the tests for the same configuration
+was in changing the 'rate-limit' parameter (and the 'option disabled' option),
+which is set to the following values: 100.0, 50.0, 10.0, 2.5 and 0.0 percent.
+Then a test is performed with the OT filter active but disabled for request
+processing ('option disabled' is included in the ot.cfg configuration).  In
+the last test, the OT filter is not used at all, ie it is not active and does
+not affect the operation of HAProxy in any way.
+
+
+6. OT CLI
+----------
+
+Via the HAProxy CLI interface we can find out the current status of the OT
+filter and change several of its settings.
+
+All supported CLI commands can be found in the following way, using the
+socat utility with the assumption that the HAProxy CLI socket path is set
+to /tmp/haproxy.sock (of course, instead of socat, nc or other utility can
+be used with a change in arguments when running the same):
+
+  % echo "help" | socat - UNIX-CONNECT:/tmp/haproxy.sock | grep flt-ot
+  --- command output ----------
+  flt-ot debug [level]   : set the OT filter debug level (default: get current debug level)
+  flt-ot disable         : disable the OT filter
+  flt-ot enable          : enable the OT filter
+  flt-ot soft-errors     : turning off hard-errors mode
+  flt-ot hard-errors     : enabling hard-errors mode
+  flt-ot logging [state] : set logging state (default: get current logging state)
+  flt-ot rate [value]    : set the rate limit (default: get current rate value)
+  flt-ot status          : show the OT filter status
+  --- command output ----------
+
+'flt-ot debug' can only be used in case the OT filter is compiled with the
+debug mode enabled.
+
+
+7. Known bugs and limitations
+------------------------------
+
+The name of the span context definition can contain only letters, numbers and
+characters '_' and '-'.  Also, all uppercase letters in the name are converted
+to lowercase.  The character '-' is converted internally to the 'D' character,
+and since a HAProxy variable is generated from that name, this should be taken
+into account if we want to use it somewhere in the HAProxy configuration.
+The above mentioned span context is used in the 'inject' and 'extract' keywords.
+
+Let's look a little at the example test/fe-be (configurations are in the
+test/fe and test/be directories, 'fe' is here the abbreviation for frontend
+and 'be' for backend).  In case we have the 'rate-limit' set to a value less
+than 100.0, then distributed tracing will not be started with each new HTTP
+request.  It also means that the span context will not be delivered (via the
+HTTP header) to the backend HAProxy process.  The 'rate-limit' on the backend
+HAProxy must be set to 100.0, but because the frontend HAProxy does not send
+a span context every time, all such cases will cause an error to be reported
+on the backend server.  Therefore, the 'hard-errors' option must be set on the
+backend server, so that processing on that stream is stopped as soon as the
+first error occurs.  Such cases will slow down the backend server's response
+a bit (in the example in question it is about 3%).
diff --git a/addons/ot/README-func b/addons/ot/README-func
new file mode 100644
index 0000000..273c7f9
--- /dev/null
+++ b/addons/ot/README-func
@@ -0,0 +1,298 @@
+Here I will write down some specifics of certain parts of the source, these are
+just some of my thoughts and clues and they are probably not too important for
+a wider audience.
+
+src/parser.c
+------------------------------------------------------------------------------
+The first thing to run when starting the HAProxy is the flt_ot_parse() function
+which actually parses the filter configuration.
+
+In case of correct configuration, the function returns ERR_NONE (or 0), while
+in case of incorrect configuration it returns the combination of ERR_* flags
+(ERR_NONE here does not belong to that bit combination because its value is 0).
+
+One of the parameters of the function is <char **err> in which an error message
+can be returned, if it exists.  In that case the return value of the function
+should have some of the ERR_* flags set.
+
+Let's look at an example of the following filter configuration what the function
+call sequence looks like.
+
+Filter configuration line:
+   filter opentracing [id <id>] config <file>
+
+Function call sequence:
+   flt_ot_parse(<err>) {
+      /* Initialization of the filter configuration data. */
+      flt_ot_conf_init() {
+      }
+
+      /* Setting the filter name. */
+      flt_ot_parse_keyword(<err>) {
+         flt_ot_parse_strdup(<err>) {
+         }
+      }
+
+      /* Setting the filter configuration file name. */
+      flt_ot_parse_keyword(<err>) {
+         flt_ot_parse_strdup(<err>) {
+         }
+      }
+
+      /* Checking the configuration of the filter. */
+      flt_ot_parse_cfg(<err>) {
+         flt_ot_parse_cfg_tracer() {
+         }
+         ...
+         flt_ot_post_parse_cfg_tracer() {
+         }
+         flt_ot_parse_cfg_group() {
+         }
+         ...
+         flt_ot_post_parse_cfg_group() {
+         }
+         flt_ot_parse_cfg_scope() {
+         }
+         ...
+         flt_ot_post_parse_cfg_scope() {
+         }
+      }
+   }
+
+Checking the filter configuration is actually much more complicated, only the
+name of the main function flt_ot_parse_cfg() that does it is listed here.
+
+All functions that use the <err> parameter should set the error status using
+that pointer.  All other functions (actually these are all functions called
+by the flt_ot_parse_cfg() function) should set the error message using the
+ha_warning()/ha_alert() HAProxy functions.  Of course, the return value (the
+mentioned combination of ERR_* bits) is set in all these functions and it
+indicates whether the filter configuration is correct or not.
+
+
+src/group.c
+------------------------------------------------------------------------------
+The OT filter allows the use of groups within which one or more 'ot-scope'
+declarations can be found.  These groups can be used using several HAProxy
+rules, more precisely 'http-request', 'http-response', 'tcp-request',
+'tcp-response' and 'http-after-response' rules.
+
+Configuration example for the specified rules:
+   <rule> ot-group <filter-id> <group-name> [ { if | unless } <condition> ]
+
+Parsing each of these rules is performed by the flt_ot_group_parse() function.
+After parsing the configuration, its verification is performed via the
+flt_ot_group_check() function.  One parsing function and one configuration
+check function are called for each defined rule.
+
+   flt_ot_group_parse(<err>) {
+   }
+   ...
+   flt_ot_group_check() {
+   }
+   ...
+
+
+When deinitializing the module, the function flt_ot_group_release() is called
+(which is actually an release_ptr callback function from one of the above
+rules).  One callback function is called for each defined rule.
+
+   flt_ot_group_release() {
+   }
+   ...
+
+
+src/filter.c
+------------------------------------------------------------------------------
+After parsing and checking the configuration, the flt_ot_check() function is
+called which associates the 'ot-group' and 'ot-scope' definitions with their
+declarations.  This procedure concludes the configuration of the OT filter and
+after that its initialization is possible.
+
+   flt_ops.check = flt_ot_check;
+   flt_ot_check() {
+   }
+
+
+The initialization of the OT filter is done via the flt_ot_init() callback
+function.  In this function the OpenTracing API library is also initialized.
+It is also possible to initialize for each thread individually, but nothing
+is being done here for now.
+
+   flt_ops.init = flt_ot_init;
+   flt_ot_init() {
+      flt_ot_cli_init() {
+      }
+      /* Initialization of the OpenTracing API. */
+      ot_init(<err>) {
+      }
+   }
+
+   flt_ops.init_per_thread = flt_ot_init_per_thread;
+   flt_ot_init_per_thread() {
+   }
+   ...
+
+
+After the filter instance is created and attached to the stream, the
+flt_ot_attach() function is called.  In this function a new OT runtime
+context is created, and flags are set that define which analyzers are used.
+
+   flt_ops.attach = flt_ot_attach;
+   flt_ot_attach() {
+      /* In case OT is disabled, nothing is done on this stream further. */
+      flt_ot_runtime_context_init(<err>) {
+         flt_ot_pool_alloc() {
+         }
+         /* Initializing and setting the variable 'sess.ot.uuid'. */
+         if (flt_ot_var_register(<err>) != -1) {
+            flt_ot_var_set(<err>) {
+            }
+         }
+      }
+   }
+
+
+When a stream is started, this function is called.  At the moment, nothing
+is being done in it.
+
+   flt_ops.stream_start = flt_ot_stream_start;
+   flt_ot_stream_start() {
+   }
+
+
+Channel analyzers are called when executing individual filter events.
+For each of the four analyzer functions, the events associated with them
+are listed.
+
+ Events:
+   -  1 'on-client-session-start'
+   - 15 'on-server-session-start'
+------------------------------------------------------------------------
+   flt_ops.channel_start_analyze = flt_ot_channel_start_analyze;
+   flt_ot_channel_start_analyze() {
+      flt_ot_event_run() {
+         /* Run event. */
+         flt_ot_scope_run() {
+            /* Processing of all ot-scopes defined for the current event. */
+         }
+      }
+   }
+
+
+ Events:
+   -  2 'on-frontend-tcp-request'
+   -  4 'on-http-body-request'
+   -  5 'on-frontend-http-request'
+   -  6 'on-switching-rules-request'
+   -  7 'on-backend-tcp-request'
+   -  8 'on-backend-http-request'
+   -  9 'on-process-server-rules-request'
+   - 10 'on-http-process-request'
+   - 11 'on-tcp-rdp-cookie-request'
+   - 12 'on-process-sticking-rules-request
+   - 16 'on-tcp-response'
+   - 18 'on-process-store-rules-response'
+   - 19 'on-http-response'
+------------------------------------------------------------------------
+   flt_ops.channel_pre_analyze = flt_ot_channel_pre_analyze;
+   flt_ot_channel_pre_analyze() {
+      flt_ot_event_run() {
+         /* Run event. */
+         flt_ot_scope_run() {
+            /* Processing of all ot-scopes defined for the current event. */
+         }
+      }
+   }
+
+
+ Events:
+   -  3 'on-http-wait-request'
+   - 17 'on-http-wait-response'
+------------------------------------------------------------------------
+   flt_ops.channel_post_analyze = flt_ot_channel_post_analyze;
+   flt_ot_channel_post_analyze() {
+      flt_ot_event_run() {
+         /* Run event. */
+         flt_ot_scope_run() {
+            /* Processing of all ot-scopes defined for the current event. */
+         }
+      }
+   }
+
+
+ Events:
+   - 13 'on-client-session-end'
+   - 14 'on-server-unavailable'
+   - 20 'on-server-session-end'
+------------------------------------------------------------------------
+   flt_ops.channel_end_analyze = flt_ot_channel_end_analyze;
+   flt_ot_channel_end_analyze() {
+      flt_ot_event_run() {
+         /* Run event. */
+         flt_ot_scope_run() {
+            /* Processing of all ot-scopes defined for the current event. */
+         }
+      }
+
+      /* In case the backend server does not work, event 'on-server-unavailable'
+         is called here before event 'on-client-session-end'. */
+      if ('on-server-unavailable') {
+         flt_ot_event_run() {
+            /* Run event. */
+            flt_ot_scope_run() {
+               /* Processing of all ot-scopes defined for the current event. */
+            }
+         }
+      }
+   }
+
+
+After the stream has stopped, this function is called.  At the moment, nothing
+is being done in it.
+
+   flt_ops.stream_stop = flt_ot_stream_stop;
+   flt_ot_stream_stop() {
+   }
+
+
+Then, before the instance filter is detached from the stream, the following
+function is called.  It deallocates the runtime context of the OT filter.
+
+   flt_ops.detach = flt_ot_detach;
+   flt_ot_detach() {
+      flt_ot_runtime_context_free() {
+         flt_ot_pool_free() {
+         }
+      }
+   }
+
+
+Module deinitialization begins with deinitialization of individual threads
+(as many threads as configured for the HAProxy process).  Because nothing
+special is connected to the process threads, nothing is done in this function.
+
+   flt_ops.deinit_per_thread = flt_ot_deinit_per_thread;
+   flt_ot_deinit_per_thread() {
+   }
+   ...
+
+
+For this function see the above description related to the src/group.c file.
+
+   flt_ot_group_release() {
+   }
+   ...
+
+
+Module deinitialization ends with the flt_ot_deinit() function, in which all
+memory occupied by module operation (and OpenTracing API operation, of course)
+is freed.
+
+   flt_ops.deinit = flt_ot_deinit;
+   flt_ot_deinit() {
+      ot_close() {
+      }
+      flt_ot_conf_free() {
+      }
+   }
diff --git a/addons/ot/README-pool b/addons/ot/README-pool
new file mode 100644
index 0000000..8164b04
--- /dev/null
+++ b/addons/ot/README-pool
@@ -0,0 +1,25 @@
+Used pools:
+
+-------------------------------+-----------------------------+-----------------------------
+          head / name          |            size             |           define
+-------------------------------+-----------------------------+-----------------------------
+ pool_head_ buffer             | global.tune.bufsize = 16384 | USE_POOL_BUFFER
+ pool_head_ trash              |                  32 + 16384 | USE_TRASH_CHUNK
+-------------------------------+-----------------------------+-----------------------------
+ pool_head_ ot_scope_span      |                          96 | USE_POOL_OT_SCOPE_SPAN
+ pool_head_ ot_scope_context   |                          64 | USE_POOL_OT_SCOPE_CONTEXT
+ pool_head_ ot_runtime_context |                         128 | USE_POOL_OT_RUNTIME_CONTEXT
+ pool_head_ ot_span_context    |                          96 | USE_POOL_OT_SPAN_CONTEXT
+-------------------------------+-----------------------------+-----------------------------
+
+By defining individual definitions in file include/config.h, it is possible to
+switch individual pools on / off.  If a particular pool is not used, memory is
+used in a 'normal' way instead, using malloc()/free() functions.
+
+This is made only from the aspect of debugging the program, i.e. comparing the
+speed of operation using different methods of working with memory.
+
+In general, it would be better to use memory pools, due to less fragmentation
+of memory space after long operation of the program.  The speed of operation
+is similar to when using standard allocation functions (when testing it was
+shown that pool use was fast by about 1%).
diff --git a/addons/ot/include/cli.h b/addons/ot/include/cli.h
new file mode 100644
index 0000000..80ed6e8
--- /dev/null
+++ b/addons/ot/include/cli.h
@@ -0,0 +1,50 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_CLI_H_
+#define _OPENTRACING_CLI_H_
+
+#define FLT_OT_CLI_CMD                 "flt-ot"
+
+#define FLT_OT_CLI_LOGGING_OFF         "off"
+#define FLT_OT_CLI_LOGGING_ON          "on"
+#define FLT_OT_CLI_LOGGING_NOLOGNORM   "dontlog-normal"
+#define FLT_OT_CLI_LOGGING_STATE(a)    ((a) & FLT_OT_LOGGING_ON) ? (((a) & FLT_OT_LOGGING_NOLOGNORM) ? "enabled, " FLT_OT_CLI_LOGGING_NOLOGNORM : "enabled") : "disabled"
+
+#define FLT_OT_CLI_MSG_CAT(a)          ((a) == NULL) ? "" : (a), ((a) == NULL) ? "" : "\n"
+
+enum FLT_OT_LOGGING_enum {
+	FLT_OT_LOGGING_OFF       = 0,
+	FLT_OT_LOGGING_ON        = 1 << 0,
+	FLT_OT_LOGGING_NOLOGNORM = 1 << 1,
+};
+
+
+void flt_ot_cli_init(void);
+
+#endif /* _OPENTRACING_CLI_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/conf.h b/addons/ot/include/conf.h
new file mode 100644
index 0000000..97df12e
--- /dev/null
+++ b/addons/ot/include/conf.h
@@ -0,0 +1,227 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_CONF_H_
+#define _OPENTRACING_CONF_H_
+
+#define FLT_OT_CONF(f)              ((struct flt_ot_conf *)FLT_CONF(f))
+#define FLT_OT_CONF_HDR_FMT         "%p:{ { '%.*s' %zu %d } "
+#define FLT_OT_CONF_HDR_ARGS(a,b)   (a), (int)(a)->b##_len, (a)->b, (a)->b##_len, (a)->cfg_line
+#define FLT_OT_STR_HDR_ARGS(a,b)    (a)->b, (a)->b##_len
+
+#define FLT_OT_DBG_CONF_SAMPLE_EXPR(f,a) \
+	FLT_OT_DBG(3, "%s%p:{ '%s' %p }", (f), (a), (a)->value, (a)->expr)
+
+#define FLT_OT_DBG_CONF_SAMPLE(f,a)               \
+	FLT_OT_DBG(3, "%s%p:{ '%s' '%s' %s %d }", \
+	           (f), (a), (a)->key, (a)->value, flt_ot_list_debug(&((a)->exprs)), (a)->num_exprs)
+
+#define FLT_OT_DBG_CONF_STR(f,a) \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "}", FLT_OT_CONF_HDR_ARGS(a, str))
+
+#define FLT_OT_DBG_CONF_CONTEXT(f,a) \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "0x%02hhx }", FLT_OT_CONF_HDR_ARGS(a, id), (a)->flags)
+
+#define FLT_OT_DBG_CONF_SPAN(f,a)                                                                                   \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "'%s' %zu %d '%s' %zu %hhu 0x%02hhx %s %s %s }",                        \
+	           FLT_OT_CONF_HDR_ARGS(a, id), FLT_OT_STR_HDR_ARGS(a, ref_id), (a)->ref_type,                      \
+	           FLT_OT_STR_HDR_ARGS(a, ctx_id), (a)->flag_root, (a)->ctx_flags, flt_ot_list_debug(&((a)->tags)), \
+	           flt_ot_list_debug(&((a)->logs)), flt_ot_list_debug(&((a)->baggages)))
+
+#define FLT_OT_DBG_CONF_SCOPE(f,a)                                                                           \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "%hhu %d %s %p %s %s %s }",                                      \
+	           FLT_OT_CONF_HDR_ARGS(a, id), (a)->flag_used, (a)->event, flt_ot_list_debug(&((a)->acls)), \
+	           (a)->cond, flt_ot_list_debug(&((a)->contexts)), flt_ot_list_debug(&((a)->spans)),         \
+	           flt_ot_list_debug(&((a)->finish)))
+
+#define FLT_OT_DBG_CONF_GROUP(f,a)                       \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "%hhu %s }", \
+	           FLT_OT_CONF_HDR_ARGS(a, id), (a)->flag_used, flt_ot_list_debug(&((a)->ph_scopes)))
+
+#define FLT_OT_DBG_CONF_PH(f,a) \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "%p }", FLT_OT_CONF_HDR_ARGS(a, id), (a)->ptr)
+
+#define FLT_OT_DBG_CONF_TRACER(f,a)                                                                                                   \
+	FLT_OT_DBG(3, f FLT_OT_CONF_HDR_FMT "'%s' '%s' %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %s %s %s }",                             \
+	           FLT_OT_CONF_HDR_ARGS(a, id), (a)->config, (a)->plugin, (a)->tracer, (a)->rate_limit, (a)->flag_harderr,            \
+	           (a)->flag_disabled, (a)->logging, &((a)->proxy_log), flt_ot_list_debug(&((a)->proxy_log.logsrvs)), (a)->analyzers, \
+	           flt_ot_list_debug(&((a)->acls)), flt_ot_list_debug(&((a)->ph_groups)), flt_ot_list_debug(&((a)->ph_scopes)))
+
+#define FLT_OT_DBG_CONF(f,a)                                                  \
+	FLT_OT_DBG(3, "%s%p:{ %p '%s' '%s' %p %s %s }",                       \
+	           (f), (a), (a)->proxy, (a)->id, (a)->cfg_file, (a)->tracer, \
+	           flt_ot_list_debug(&((a)->groups)), flt_ot_list_debug(&((a)->scopes)))
+
+#define FLT_OT_STR_HDR(a)        \
+	struct {                 \
+		char   *a;       \
+		size_t  a##_len; \
+	}
+
+#define FLT_OT_CONF_HDR(a)            \
+	struct {                      \
+		FLT_OT_STR_HDR(a);    \
+		int         cfg_line; \
+		struct list list;     \
+	}
+
+
+struct flt_ot_conf_hdr {
+	FLT_OT_CONF_HDR(id);
+};
+
+/* flt_ot_conf_sample->exprs */
+struct flt_ot_conf_sample_expr {
+	FLT_OT_CONF_HDR(value);   /* The sample value. */
+	struct sample_expr *expr; /* The sample expression. */
+};
+
+/*
+ * flt_ot_conf_span->tags
+ * flt_ot_conf_span->logs
+ * flt_ot_conf_span->baggages
+ */
+struct flt_ot_conf_sample {
+	FLT_OT_CONF_HDR(key);   /* The sample name. */
+	char        *value;     /* The sample content. */
+	struct list  exprs;     /* Used to chain sample expressions. */
+	int          num_exprs; /* Number of defined expressions. */
+};
+
+/* flt_ot_conf_scope->finish */
+struct flt_ot_conf_str {
+	FLT_OT_CONF_HDR(str); /* String content/length. */
+};
+
+/* flt_ot_conf_scope->contexts */
+struct flt_ot_conf_context {
+	FLT_OT_CONF_HDR(id); /* The name of the context. */
+	uint8_t flags;       /* The type of storage from which the span context is extracted.  */
+};
+
+/* flt_ot_conf_scope->spans */
+struct flt_ot_conf_span {
+	FLT_OT_CONF_HDR(id);    /* The name of the span. */
+	FLT_OT_STR_HDR(ref_id); /* The reference name, if used. */
+	int         ref_type;   /* The reference type. */
+	FLT_OT_STR_HDR(ctx_id); /* The span context name, if used. */
+	uint8_t     ctx_flags;  /* The type of storage used for the span context. */
+	bool        flag_root;  /* Whether this is a root span. */
+	struct list tags;       /* The set of key:value tags. */
+	struct list logs;       /* The set of key:value logs. */
+	struct list baggages;   /* The set of key:value baggage items. */
+};
+
+struct flt_ot_conf_scope {
+	FLT_OT_CONF_HDR(id);        /* The scope name. */
+	bool             flag_used; /* The indication that the scope is being used. */
+	int              event;     /* FLT_OT_EVENT_* */
+	struct list      acls;      /* ACLs declared on this scope. */
+	struct acl_cond *cond;      /* ACL condition to meet. */
+	struct list      contexts;  /* Declared contexts. */
+	struct list      spans;     /* Declared spans. */
+	struct list      finish;    /* The list of spans to be finished. */
+};
+
+struct flt_ot_conf_group {
+	FLT_OT_CONF_HDR(id);   /* The group name. */
+	bool        flag_used; /* The indication that the group is being used. */
+	struct list ph_scopes; /* List of all used scopes. */
+};
+
+struct flt_ot_conf_ph {
+	FLT_OT_CONF_HDR(id); /* The scope/group name. */
+	void *ptr;           /* Pointer to real placeholder structure. */
+};
+#define flt_ot_conf_ph_group      flt_ot_conf_ph
+#define flt_ot_conf_ph_scope      flt_ot_conf_ph
+
+struct flt_ot_conf_tracer {
+	FLT_OT_CONF_HDR(id);              /* The tracer name. */
+	char              *config;        /* The OpenTracing configuration file name. */
+	char              *plugin;        /* The OpenTracing plugin library file name. */
+	struct otc_tracer *tracer;        /* The OpenTracing tracer handle. */
+	uint32_t           rate_limit;    /* [0 2^32-1] <-> [0.0 100.0] */
+	bool               flag_harderr;  /* [0 1] */
+	bool               flag_disabled; /* [0 1] */
+	uint8_t            logging;       /* [0 1 3] */
+	struct proxy       proxy_log;     /* The log server list. */
+	uint               analyzers;     /* Defined channel analyzers. */
+	struct list        acls;          /* ACLs declared on this tracer. */
+	struct list        ph_groups;     /* List of all used groups. */
+	struct list        ph_scopes;     /* List of all used scopes. */
+};
+
+struct flt_ot_counters {
+#ifdef DEBUG_OT
+	struct {
+		bool     flag_used; /* Whether this event is used. */
+		uint64_t htx[2];    /* htx_is_empty() function result counter. */
+	} event[FLT_OT_EVENT_MAX];
+#endif
+
+	uint64_t disabled[2];       /* How many times stream processing is disabled. */
+};
+
+/* The OpenTracing filter configuration. */
+struct flt_ot_conf {
+	struct proxy              *proxy;    /* Proxy owning the filter. */
+	char                      *id;       /* The OpenTracing filter id. */
+	char                      *cfg_file; /* The OpenTracing filter configuration file name. */
+	struct flt_ot_conf_tracer *tracer;   /* There can only be one tracer. */
+	struct list                groups;   /* List of all available groups. */
+	struct list                scopes;   /* List of all available scopes. */
+	struct flt_ot_counters     cnt;      /* Various counters related to filter operation. */
+};
+
+
+#define flt_ot_conf_ph_group_free   flt_ot_conf_ph_free
+#define flt_ot_conf_ph_scope_free   flt_ot_conf_ph_free
+
+struct flt_ot_conf_ph          *flt_ot_conf_ph_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_ph_free(struct flt_ot_conf_ph **ptr);
+struct flt_ot_conf_sample_expr *flt_ot_conf_sample_expr_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_sample_expr_free(struct flt_ot_conf_sample_expr **ptr);
+struct flt_ot_conf_sample      *flt_ot_conf_sample_init(char **args, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_sample_free(struct flt_ot_conf_sample **ptr);
+struct flt_ot_conf_str         *flt_ot_conf_str_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_str_free(struct flt_ot_conf_str **ptr);
+struct flt_ot_conf_context     *flt_ot_conf_context_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_context_free(struct flt_ot_conf_context **ptr);
+struct flt_ot_conf_span        *flt_ot_conf_span_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_span_free(struct flt_ot_conf_span **ptr);
+struct flt_ot_conf_scope       *flt_ot_conf_scope_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_scope_free(struct flt_ot_conf_scope **ptr);
+struct flt_ot_conf_group       *flt_ot_conf_group_init(const char *id, int linenum, struct list *head, char **err);
+void                            flt_ot_conf_group_free(struct flt_ot_conf_group **ptr);
+struct flt_ot_conf_tracer      *flt_ot_conf_tracer_init(const char *id, int linenum, char **err);
+void                            flt_ot_conf_tracer_free(struct flt_ot_conf_tracer **ptr);
+struct flt_ot_conf             *flt_ot_conf_init(struct proxy *px);
+void                            flt_ot_conf_free(struct flt_ot_conf **ptr);
+
+#endif /* _OPENTRACING_CONF_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/config.h b/addons/ot/include/config.h
new file mode 100644
index 0000000..3b26365
--- /dev/null
+++ b/addons/ot/include/config.h
@@ -0,0 +1,46 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_CONFIG_H_
+#define _OPENTRACING_CONFIG_H_
+
+#undef  DEBUG_OT_SYSTIME
+#define USE_POOL_BUFFER
+#define USE_POOL_OT_SPAN_CONTEXT
+#define USE_POOL_OT_SCOPE_SPAN
+#define USE_POOL_OT_SCOPE_CONTEXT
+#define USE_POOL_OT_RUNTIME_CONTEXT
+#define USE_TRASH_CHUNK
+
+#define FLT_OT_ID_MAXLEN        64
+#define FLT_OT_MAXTAGS          8
+#define FLT_OT_MAXBAGGAGES      8
+#define FLT_OT_RATE_LIMIT_MAX   100.0
+#define FLT_OT_DEBUG_LEVEL      0b00001111
+
+#endif /* _OPENTRACING_CONFIG_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/debug.h b/addons/ot/include/debug.h
new file mode 100644
index 0000000..7becdf7
--- /dev/null
+++ b/addons/ot/include/debug.h
@@ -0,0 +1,96 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_DEBUG_H_
+#define _OPENTRACING_DEBUG_H_
+
+#ifdef DEBUG_FULL
+#  define DEBUG_OT
+#endif
+
+#ifdef DEBUG_OT
+#  ifdef DEBUG_OT_SYSTIME
+#     define FLT_OT_DBG_FMT(f)      "[% 2d] %ld.%06ld [" FLT_OT_SCOPE "]: " f, tid, now.tv_sec, now.tv_usec
+#  else
+#     define FLT_OT_DBG_FMT(f)      "[% 2d] %11.6f [" FLT_OT_SCOPE "]: " f, tid, FLT_OT_TV_UDIFF(&(flt_ot_debug.start), &now) / 1e6
+#  endif
+#  define FLT_OT_DBG_INDENT         "                                                                                "
+#  define FLT_OT_DBG(l,f, ...)                                                             \
+	do {                                                                               \
+		if (!(l) || (flt_ot_debug.level & (1 << (l))))                             \
+			(void)fprintf(stderr, FLT_OT_DBG_FMT("%.*s" f "\n"),               \
+			              dbg_indent_level, FLT_OT_DBG_INDENT, ##__VA_ARGS__); \
+	} while (0)
+#  define FLT_OT_FUNC(f, ...)       do { FLT_OT_DBG(1, "%s(" f ") {", __func__, ##__VA_ARGS__); dbg_indent_level += 3; } while (0)
+#  define FLT_OT_RETURN(a)          do { dbg_indent_level -= 3; FLT_OT_DBG(1, "}"); return a; } while (0)
+#  define FLT_OT_DBG_IFDEF(a,b)     a
+#  define FLT_OT_DBG_ARGS(a, ...)   a, ##__VA_ARGS__
+
+struct flt_ot_debug {
+#ifndef DEBUG_OT_SYSTIME
+	struct timeval start;
+#endif
+	uint8_t        level;
+};
+
+
+extern THREAD_LOCAL int    dbg_indent_level;
+extern struct flt_ot_debug flt_ot_debug;
+
+#else
+
+#  define FLT_OT_DBG(...)           while (0)
+#  define FLT_OT_FUNC(...)          while (0)
+#  define FLT_OT_RETURN(a)          return a
+#  define FLT_OT_DBG_IFDEF(a,b)     b
+#  define FLT_OT_DBG_ARGS(...)
+#endif /* DEBUG_OT */
+
+/*
+ *  ON  | NOLOGNORM |
+ * -----+-----------+-------------
+ *   0  |     0     |  no log
+ *   0  |     1     |  no log
+ *   1  |     0     |  log all
+ *   1  |     1     |  log errors
+ * -----+-----------+-------------
+ */
+#define FLT_OT_LOG(l,f, ...)                                                                                                    \
+	do {                                                                                                                    \
+		if (!(conf->tracer->logging & FLT_OT_LOGGING_ON))                                                               \
+			FLT_OT_DBG(3, "NOLOG[%d]: [" FLT_OT_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__);                  \
+		else if ((conf->tracer->logging & FLT_OT_LOGGING_NOLOGNORM) && ((l) > LOG_ERR))                                 \
+			FLT_OT_DBG(2, "NOLOG[%d]: [" FLT_OT_SCOPE "]: [%s] " f, (l), conf->id, ##__VA_ARGS__);                  \
+		else {                                                                                                          \
+			send_log(&(conf->tracer->proxy_log), (l), "[" FLT_OT_SCOPE "]: [%s] " f "\n", conf->id, ##__VA_ARGS__); \
+                                                                                                                                \
+			FLT_OT_DBG(1, "LOG[%d]: %s", (l), logline);                                                             \
+		}                                                                                                               \
+	} while (0)
+
+#endif /* _OPENTRACING_DEBUG_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/define.h b/addons/ot/include/define.h
new file mode 100644
index 0000000..8769161
--- /dev/null
+++ b/addons/ot/include/define.h
@@ -0,0 +1,104 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_DEFINE_H_
+#define _OPENTRACING_DEFINE_H_
+
+#define FLT_OT_DEREF(a,m,v)        (((a) != NULL) ? (a)->m : (v))
+#define FLT_OT_DDEREF(a,m,v)       ((((a) != NULL) && (*(a) != NULL)) ? (*(a))->m : (v))
+#define FLT_OT_TABLESIZE(a)        (sizeof(a) / sizeof((a)[0]))
+#define FLT_OT_IN_RANGE(v,a,b)     (((v) >= (a)) && ((v) <= (b)))
+#define FLT_OT_DPTR_ARGS(a)        (a), ((a) == NULL) ? NULL : *(a)
+#define FLT_OT_ARG_ISVALID(n)      ((args[n] != NULL) && *args[n])
+#define FLT_OT_TV_UDIFF(a,b)       (((b)->tv_sec - (a)->tv_sec) * 1000000 + (b)->tv_usec - (a)->tv_usec)
+#define FLT_OT_U32_FLOAT(a,b)      ((a) * (double)(b) / UINT32_MAX)
+#define FLT_OT_FLOAT_U32(a,b)      ((uint32_t)((a) / (double)(b) * UINT32_MAX + 0.5))
+
+#define FLT_OT_STR_DASH_72         "------------------------------------------------------------------------"
+#define FLT_OT_STR_DASH_78         FLT_OT_STR_DASH_72 "------"
+#define FLT_OT_STR_FLAG_YN(a)      (a) ? "yes" : "no"
+
+#define FLT_OT_STR_SIZE(a)         (sizeof(a) - 1)
+#define FLT_OT_STR_ADDRSIZE(a)     (a), FLT_OT_STR_SIZE(a)
+#define FLT_OT_STR_ISVALID(a)      (((a) != NULL) && (*(a) != '\0'))
+#define FLT_OT_STR_CMP(S,s,l)      (((l) == FLT_OT_STR_SIZE(S)) && (memcmp((s), FLT_OT_STR_ADDRSIZE(S)) == 0))
+#define FLT_OT_STR_ELLIPSIS(a,n)   do { if ((a) != NULL) { if ((n) > 0) (a)[(n) - 1] = '\0'; if ((n) > 3) (a)[(n) - 2] = (a)[(n) - 3] = (a)[(n) - 4] = '.'; } } while (0)
+#define FLT_OT_NIBBLE_TO_HEX(a)    ((a) + (((a) < 10) ? '0' : ('a' - 10)))
+
+#define FLT_OT_FREE(a)             do { if ((a) != NULL) OTC_DBG_FREE(a); } while (0)
+#define FLT_OT_FREE_VOID(a)        do { if ((a) != NULL) OTC_DBG_FREE((void *)(a)); } while (0)
+#define FLT_OT_FREE_CLEAR(a)       do { if ((a) != NULL) { OTC_DBG_FREE(a); (a) = NULL; } } while (0)
+#define FLT_OT_STRDUP(s)           OTC_DBG_STRDUP(s)
+#define FLT_OT_STRNDUP(s,n)        OTC_DBG_STRNDUP((s), (n))
+#define FLT_OT_CALLOC(n,e)         OTC_DBG_CALLOC((n), (e))
+#define FLT_OT_MALLOC(s)           OTC_DBG_MALLOC((s))
+#define FLT_OT_MEMINFO()           OTC_DBG_MEMINFO()
+
+#define FLT_OT_RUN_ONCE(f)         do { static bool __f = 1; if (__f) { __f = 0; f; } } while (0)
+
+#define FLT_OT_LIST_ISVALID(a)     (((a) != NULL) && ((a)->n != NULL) && ((a)->p != NULL))
+#define FLT_OT_LIST_DEL(a)         do { if (FLT_OT_LIST_ISVALID(a)) LIST_DEL(a); } while (0)
+#define FLT_OT_LIST_DESTROY(t,h)                                                  \
+	do {                                                                      \
+		struct flt_ot_conf_##t *_ptr, *_back;                             \
+                                                                                  \
+		if (!FLT_OT_LIST_ISVALID(h) || LIST_ISEMPTY(h))                   \
+			break;                                                    \
+                                                                                  \
+		FLT_OT_DBG(2, "- deleting " #t " list %s", flt_ot_list_debug(h)); \
+                                                                                  \
+		list_for_each_entry_safe(_ptr, _back, (h), list)                  \
+			flt_ot_conf_##t##_free(&_ptr);                            \
+	} while (0)
+
+#define FLT_OT_BUFFER_THR(b,m,n,p)                \
+	static THREAD_LOCAL char    b[m][n];      \
+	static THREAD_LOCAL size_t  __idx = 0;    \
+	char                       *p = b[__idx]; \
+	__idx = (__idx + 1) % (m)
+
+#define FLT_OT_ERR(f, ...)                                      \
+	do {                                                    \
+		if ((err != NULL) && (*err == NULL))            \
+			(void)memprintf(err, f, ##__VA_ARGS__); \
+	} while (0)
+#define FLT_OT_ERR_APPEND(f, ...)                               \
+	do {                                                    \
+		if (err != NULL)                                \
+			(void)memprintf(err, f, ##__VA_ARGS__); \
+	} while (0)
+#define FLT_OT_ERR_FREE(p)                                                  \
+	do {                                                                \
+		if ((p) == NULL)                                            \
+			break;                                              \
+                                                                            \
+		FLT_OT_DBG(0, "%s:%d: ERROR: %s", __func__, __LINE__, (p)); \
+		FLT_OT_FREE_CLEAR(p);                                       \
+	} while (0)
+
+#endif /* _OPENTRACING_DEFINE_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/event.h b/addons/ot/include/event.h
new file mode 100644
index 0000000..8d59163
--- /dev/null
+++ b/addons/ot/include/event.h
@@ -0,0 +1,120 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_EVENT_H_
+#define _OPENTRACING_EVENT_H_
+
+/*
+ * This must be defined in order for macro FLT_OT_EVENT_DEFINES
+ * and structure flt_ot_event_data to have the correct contents.
+ */
+#define AN_REQ_NONE                 0
+#define AN_REQ_CLIENT_SESS_START    0
+#define AN_REQ_SERVER_UNAVAILABLE   0
+#define AN_REQ_CLIENT_SESS_END      0
+#define AN_RES_SERVER_SESS_START    0
+#define AN_RES_SERVER_SESS_END      0
+#define SMP_VAL_FE_                 0
+#define SMP_VAL_BE_                 0
+
+/*
+ * Event names are selected to be somewhat compatible with the SPOE filter,
+ * from which the following names are taken:
+ *   - on-client-session -> on-client-session-start
+ *   - on-frontend-tcp-request
+ *   - on-frontend-http-request
+ *   - on-backend-tcp-request
+ *   - on-backend-http-request
+ *   - on-server-session -> on-server-session-start
+ *   - on-tcp-response
+ *   - on-http-response
+ *
+ * FLT_OT_EVENT_NONE is used as an index for 'ot-scope' sections that do not
+ * have an event defined.  The 'ot-scope' sections thus defined can be used
+ * within the 'ot-group' section.
+ *
+ * A description of the macro arguments can be found in the structure
+ * flt_ot_event_data definition
+ */
+#define FLT_OT_EVENT_DEFINES                                                                                \
+	FLT_OT_EVENT_DEF(              NONE, REQ,        ,        , 0, "")                                  \
+	FLT_OT_EVENT_DEF( CLIENT_SESS_START, REQ, CON_ACC,        , 1, "on-client-session-start")           \
+	FLT_OT_EVENT_DEF(        INSPECT_FE, REQ, REQ_CNT,        , 1, "on-frontend-tcp-request")           \
+	FLT_OT_EVENT_DEF(         WAIT_HTTP, REQ,        ,        , 1, "on-http-wait-request")              \
+	FLT_OT_EVENT_DEF(         HTTP_BODY, REQ,        ,        , 1, "on-http-body-request")              \
+	FLT_OT_EVENT_DEF(   HTTP_PROCESS_FE, REQ, HRQ_HDR,        , 1, "on-frontend-http-request")          \
+	FLT_OT_EVENT_DEF(   SWITCHING_RULES, REQ,        ,        , 1, "on-switching-rules-request")        \
+	FLT_OT_EVENT_DEF(        INSPECT_BE, REQ, REQ_CNT, REQ_CNT, 1, "on-backend-tcp-request")            \
+	FLT_OT_EVENT_DEF(   HTTP_PROCESS_BE, REQ, HRQ_HDR, HRQ_HDR, 1, "on-backend-http-request")           \
+/*	FLT_OT_EVENT_DEF(       HTTP_TARPIT, REQ,        ,        , 1, "on-http-tarpit-request") */         \
+	FLT_OT_EVENT_DEF(         SRV_RULES, REQ,        ,        , 1, "on-process-server-rules-request")   \
+	FLT_OT_EVENT_DEF(        HTTP_INNER, REQ,        ,        , 1, "on-http-process-request")           \
+	FLT_OT_EVENT_DEF(   PRST_RDP_COOKIE, REQ,        ,        , 1, "on-tcp-rdp-cookie-request")         \
+	FLT_OT_EVENT_DEF(    STICKING_RULES, REQ,        ,        , 1, "on-process-sticking-rules-request") \
+	FLT_OT_EVENT_DEF(   CLIENT_SESS_END, REQ,        ,        , 0, "on-client-session-end")             \
+	FLT_OT_EVENT_DEF(SERVER_UNAVAILABLE, REQ,        ,        , 0, "on-server-unavailable")             \
+                                                                                                            \
+	FLT_OT_EVENT_DEF( SERVER_SESS_START, RES,        , SRV_CON, 0, "on-server-session-start")           \
+	FLT_OT_EVENT_DEF(           INSPECT, RES, RES_CNT, RES_CNT, 0, "on-tcp-response")                   \
+	FLT_OT_EVENT_DEF(         WAIT_HTTP, RES,        ,        , 1, "on-http-wait-response")             \
+	FLT_OT_EVENT_DEF(       STORE_RULES, RES,        ,        , 1, "on-process-store-rules-response")   \
+	FLT_OT_EVENT_DEF(   HTTP_PROCESS_BE, RES, HRS_HDR, HRS_HDR, 1, "on-http-response")                  \
+	FLT_OT_EVENT_DEF(   SERVER_SESS_END, RES,        ,        , 0, "on-server-session-end")
+
+enum FLT_OT_EVENT_enum {
+#define FLT_OT_EVENT_DEF(a,b,c,d,e,f)   FLT_OT_EVENT_##b##_##a,
+	FLT_OT_EVENT_DEFINES
+	FLT_OT_EVENT_MAX
+#undef FLT_OT_EVENT_DEF
+};
+
+enum FLT_OT_EVENT_SAMPLE_enum {
+	FLT_OT_EVENT_SAMPLE_TAG = 0,
+	FLT_OT_EVENT_SAMPLE_LOG,
+	FLT_OT_EVENT_SAMPLE_BAGGAGE,
+};
+
+struct flt_ot_event_data {
+	uint        an_bit;           /* Used channel analyser. */
+	uint        smp_opt_dir;      /* Fetch direction (request/response). */
+	uint        smp_val_fe;       /* Valid FE fetch location. */
+	uint        smp_val_be;       /* Valid BE fetch location. */
+	bool        flag_http_inject; /* Span context injection allowed. */
+	const char *name;             /* Filter event name. */
+};
+
+struct flt_ot_conf_scope;
+
+
+extern const struct flt_ot_event_data flt_ot_event_data[FLT_OT_EVENT_MAX];
+
+
+int flt_ot_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_ot_conf_scope *conf_scope, const struct timespec *ts, uint dir, char **err);
+int flt_ot_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err);
+
+#endif /* _OPENTRACING_EVENT_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/filter.h b/addons/ot/include/filter.h
new file mode 100644
index 0000000..0b36354
--- /dev/null
+++ b/addons/ot/include/filter.h
@@ -0,0 +1,68 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_FILTER_H_
+#define _OPENTRACING_FILTER_H_
+
+#define FLT_OT_FMT_NAME           "'" FLT_OT_OPT_NAME "' : "
+#define FLT_OT_FMT_TYPE           "'filter' : "
+#define FTL_OT_VAR_UUID           "sess", "ot", "uuid"
+#define FLT_OT_ALERT(f, ...)      ha_alert(FLT_OT_FMT_TYPE FLT_OT_FMT_NAME f "\n", ##__VA_ARGS__)
+
+#define FLT_OT_CONDITION_IF       "if"
+#define FLT_OT_CONDITION_UNLESS   "unless"
+
+enum FLT_OT_RET_enum {
+	FLT_OT_RET_ERROR  = -1,
+	FLT_OT_RET_WAIT   = 0,
+	FLT_OT_RET_IGNORE = 0,
+	FLT_OT_RET_OK     = 1,
+};
+
+#define FLT_OT_DBG_LIST(d,m,p,t,v,f)                                 \
+	do {                                                         \
+		if (LIST_ISEMPTY(&((d)->m##s))) {                    \
+			FLT_OT_DBG(3, p "- no " #m "s " t);          \
+		} else {                                             \
+			const struct flt_ot_conf_##m *v;             \
+                                                                     \
+			FLT_OT_DBG(3, p "- " t " " #m "s: %s",       \
+			           flt_ot_list_debug(&((d)->m##s))); \
+			list_for_each_entry(v, &((d)->m##s), list)   \
+				do { f; } while (0);                 \
+		}                                                    \
+	} while (0)
+
+
+extern const char     *ot_flt_id;
+extern struct flt_ops  flt_ot_ops;
+
+
+bool flt_ot_is_disabled(const struct filter *f FLT_OT_DBG_ARGS(, int event));
+
+#endif /* _OPENTRACING_FILTER_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/group.h b/addons/ot/include/group.h
new file mode 100644
index 0000000..a9bfcc6
--- /dev/null
+++ b/addons/ot/include/group.h
@@ -0,0 +1,61 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_GROUP_H_
+#define _OPENTRACING_GROUP_H_
+
+#define FLT_OT_ACTION_GROUP   "ot-group"
+
+enum FLT_OT_ARG_enum {
+	FLT_OT_ARG_FILTER_ID = 0,
+	FLT_OT_ARG_GROUP_ID,
+
+	FLT_OT_ARG_FLT_CONF = 0,
+	FLT_OT_ARG_CONF,
+	FLT_OT_ARG_GROUP,
+};
+
+/*
+ * A description of the macro arguments can be found in the structure
+ * flt_ot_group_data definition
+ */
+#define FLT_OT_GROUP_DEFINES                                                     \
+	FLT_OT_GROUP_DEF(ACT_F_TCP_REQ_CON, SMP_VAL_FE_CON_ACC, SMP_OPT_DIR_REQ) \
+	FLT_OT_GROUP_DEF(ACT_F_TCP_REQ_SES, SMP_VAL_FE_SES_ACC, SMP_OPT_DIR_REQ) \
+	FLT_OT_GROUP_DEF(ACT_F_TCP_REQ_CNT, SMP_VAL_FE_REQ_CNT, SMP_OPT_DIR_REQ) \
+	FLT_OT_GROUP_DEF(ACT_F_TCP_RES_CNT, SMP_VAL_BE_RES_CNT, SMP_OPT_DIR_RES) \
+	FLT_OT_GROUP_DEF(ACT_F_HTTP_REQ,    SMP_VAL_FE_HRQ_HDR, SMP_OPT_DIR_REQ) \
+	FLT_OT_GROUP_DEF(ACT_F_HTTP_RES,    SMP_VAL_BE_HRS_HDR, SMP_OPT_DIR_RES)
+
+struct flt_ot_group_data {
+	enum act_from act_from;    /* ACT_F_* */
+	uint          smp_val;     /* Valid FE/BE fetch location. */
+	uint          smp_opt_dir; /* Fetch direction (request/response). */
+};
+
+#endif /* _OPENTRACING_GROUP_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/http.h b/addons/ot/include/http.h
new file mode 100644
index 0000000..c323cde
--- /dev/null
+++ b/addons/ot/include/http.h
@@ -0,0 +1,41 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_HTTP_H_
+#define _OPENTRACING_HTTP_H_
+
+#ifndef DEBUG_OT
+#  define flt_ot_http_headers_dump(...)   while (0)
+#else
+void                 flt_ot_http_headers_dump(const struct channel *chn);
+#endif
+struct otc_text_map *flt_ot_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err);
+int                  flt_ot_http_header_set(struct channel *chn, const char *prefix, const char *name, const char *value, char **err);
+int                  flt_ot_http_headers_remove(struct channel *chn, const char *prefix, char **err);
+
+#endif /* _OPENTRACING_HTTP_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/include.h b/addons/ot/include/include.h
new file mode 100644
index 0000000..3c0d110
--- /dev/null
+++ b/addons/ot/include/include.h
@@ -0,0 +1,63 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_INCLUDE_H_
+#define _OPENTRACING_INCLUDE_H_
+
+#include <errno.h>
+#include <stdbool.h>
+
+#include <haproxy/api.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/acl.h>
+#include <haproxy/cli.h>
+#include <haproxy/filters.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/http_rules.h>
+#include <haproxy/log.h>
+#include <haproxy/sample.h>
+#include <haproxy/tcp_rules.h>
+#include <haproxy/vars.h>
+
+#include "config.h"
+#include "debug.h"
+#include "define.h"
+#include "cli.h"
+#include "event.h"
+#include "conf.h"
+#include "filter.h"
+#include "group.h"
+#include "http.h"
+#include "opentracing.h"
+#include "parser.h"
+#include "pool.h"
+#include "scope.h"
+#include "util.h"
+#include "vars.h"
+
+#endif /* _OPENTRACING_INCLUDE_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/opentracing.h b/addons/ot/include/opentracing.h
new file mode 100644
index 0000000..2dbf532
--- /dev/null
+++ b/addons/ot/include/opentracing.h
@@ -0,0 +1,85 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_OT_H_
+#define _OPENTRACING_OT_H_
+
+#include <opentracing-c-wrapper/include.h>
+
+
+#define FLT_OT_VSET(p,t,v) \
+	do { (p)->type = otc_value_##t; (p)->value.t##_value = (v); } while (0)
+
+#define FLT_OT_DBG_TEXT_MAP(a)                     \
+	FLT_OT_DBG(3, "%p:{ %p %p %zu/%zu %hhu }", \
+	           (a), (a)->key, (a)->value, (a)->count, (a)->size, (a)->is_dynamic)
+
+#define FLT_OT_DBG_TEXT_CARRIER(a,f)                                                 \
+	FLT_OT_DBG(3, "%p:{ { %p %p %zu/%zu %hhu } %p }",                            \
+	           (a), (a)->text_map.key, (a)->text_map.value, (a)->text_map.count, \
+	           (a)->text_map.size, (a)->text_map.is_dynamic, (a)->f)
+
+#define FLT_OT_DBG_CUSTOM_CARRIER(a,f)                                \
+	FLT_OT_DBG(3, "%p:{ { %p %zu %hhu } %p }",                    \
+	           (a), (a)->binary_data.data, (a)->binary_data.size, \
+	           (a)->binary_data.is_dynamic, (a)->f)
+
+#define FLT_OT_DBG_SPAN_CONTEXT(a) \
+	FLT_OT_DBG(3, "%p:{ %" PRId64 " %p %p }", (a), (a)->idx, (a)->span, (a)->destroy)
+
+
+#ifndef DEBUG_OT
+#  define ot_debug()              while (0)
+#  define ot_text_map_show(...)   while (0)
+#else
+void                     ot_text_map_show(const struct otc_text_map *text_map);
+void                     ot_debug(void);
+#endif
+int                      ot_init(struct otc_tracer **tracer, const char *config, const char *plugin, char **err);
+struct otc_span         *ot_span_init(struct otc_tracer *tracer, const char *operation_name, const struct timespec *ts_steady, const struct timespec *ts_system, int ref_type, int ref_ctx_idx, const struct otc_span *ref_span, const struct otc_tag *tags, int num_tags, char **err);
+int                      ot_span_tag(struct otc_span *span, const struct otc_tag *tags, int num_tags);
+int                      ot_span_log(struct otc_span *span, const struct otc_log_field *log_fields, int num_fields);
+int                      ot_span_set_baggage(struct otc_span *span, const struct otc_text_map *baggage);
+struct otc_span_context *ot_inject_http_headers(struct otc_tracer *tracer, const struct otc_span *span, struct otc_http_headers_writer *carrier, char **err);
+struct otc_span_context *ot_extract_http_headers(struct otc_tracer *tracer, struct otc_http_headers_reader *carrier, const struct otc_text_map *text_map, char **err);
+void                     ot_span_finish(struct otc_span **span, const struct timespec *ts_finish, const struct timespec *log_ts, const char *log_key, const char *log_value, ...);
+void                     ot_close(struct otc_tracer **tracer);
+
+/* Unused code. */
+struct otc_span         *ot_span_init_va(struct otc_tracer *tracer, const char *operation_name, const struct timespec *ts_steady, const struct timespec *ts_system, int ref_type, int ref_ctx_idx, const struct otc_span *ref_span, char **err, const char *tag_key, const char *tag_value, ...);
+int                      ot_span_tag_va(struct otc_span *span, const char *key, int type, ...);
+int                      ot_span_log_va(struct otc_span *span, const char *key, const char *value, ...);
+int                      ot_span_log_fmt(struct otc_span *span, const char *key, const char *format, ...) __attribute__ ((format(printf, 3, 4)));
+int                      ot_span_set_baggage_va(struct otc_span *span, const char *key, const char *value, ...);
+struct otc_text_map     *ot_span_baggage_va(const struct otc_span *span, const char *key, ...);
+struct otc_span_context *ot_inject_text_map(struct otc_tracer *tracer, const struct otc_span *span, struct otc_text_map_writer *carrier);
+struct otc_span_context *ot_inject_binary(struct otc_tracer *tracer, const struct otc_span *span, struct otc_custom_carrier_writer *carrier);
+struct otc_span_context *ot_extract_text_map(struct otc_tracer *tracer, struct otc_text_map_reader *carrier, const struct otc_text_map *text_map);
+struct otc_span_context *ot_extract_binary(struct otc_tracer *tracer, struct otc_custom_carrier_reader *carrier, const struct otc_binary_data *binary_data);
+
+#endif /* _OPENTRACING_OT_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/parser.h b/addons/ot/include/parser.h
new file mode 100644
index 0000000..5e7b298
--- /dev/null
+++ b/addons/ot/include/parser.h
@@ -0,0 +1,148 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_PARSER_H_
+#define _OPENTRACING_PARSER_H_
+
+#define FLT_OT_SCOPE                        "OT"
+
+/*
+ * filter FLT_OT_OPT_NAME FLT_OT_OPT_FILTER_ID <FLT_OT_OPT_FILTER_ID_DEFAULT> FLT_OT_OPT_CONFIG <file>
+ */
+#define FLT_OT_OPT_NAME                     "opentracing"
+#define FLT_OT_OPT_FILTER_ID                "id"
+#define FLT_OT_OPT_FILTER_ID_DEFAULT        "ot-filter"
+#define FLT_OT_OPT_CONFIG                   "config"
+
+#define FLT_OT_PARSE_SECTION_TRACER_ID      "ot-tracer"
+#define FLT_OT_PARSE_SECTION_GROUP_ID       "ot-group"
+#define FLT_OT_PARSE_SECTION_SCOPE_ID       "ot-scope"
+
+#define FLT_OT_PARSE_SPAN_ROOT              "root"
+#define FLT_OT_PARSE_SPAN_REF_CHILD         "child-of"
+#define FLT_OT_PARSE_SPAN_REF_FOLLOWS       "follows-from"
+#define FLT_OT_PARSE_CTX_AUTONAME           "-"
+#define FLT_OT_PARSE_CTX_USE_HEADERS        "use-headers"
+#define FLT_OT_PARSE_CTX_USE_VARS           "use-vars"
+#define FLT_OT_PARSE_OPTION_HARDERR         "hard-errors"
+#define FLT_OT_PARSE_OPTION_DISABLED        "disabled"
+#define FLT_OT_PARSE_OPTION_NOLOGNORM       "dontlog-normal"
+
+/*
+ * A description of the macro arguments can be found in the structure
+ * flt_ot_parse_data definition
+ */
+#define FLT_OT_PARSE_TRACER_DEFINES                                                                                                                          \
+	FLT_OT_PARSE_TRACER_DEF(         ID, 0, 1, 2, 2, "ot-tracer",   " <name>")                                                                           \
+	FLT_OT_PARSE_TRACER_DEF(        ACL, 0, 1, 3, 0, "acl",         " <name> <criterion> [flags] [operator] <value> ...")                                \
+	FLT_OT_PARSE_TRACER_DEF(        LOG, 0, 1, 2, 0, "log",         " { global | <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]] }") \
+	FLT_OT_PARSE_TRACER_DEF(     CONFIG, 0, 0, 2, 2, "config",      " <file>")                                                                           \
+	FLT_OT_PARSE_TRACER_DEF(     PLUGIN, 0, 0, 2, 2, "plugin",      " <file>")                                                                           \
+	FLT_OT_PARSE_TRACER_DEF(     GROUPS, 0, 0, 2, 0, "groups",      " <name> ...")                                                                       \
+	FLT_OT_PARSE_TRACER_DEF(     SCOPES, 0, 0, 2, 0, "scopes",      " <name> ...")                                                                       \
+	FLT_OT_PARSE_TRACER_DEF( RATE_LIMIT, 0, 0, 2, 2, "rate-limit",  " <value>")                                                                          \
+	FLT_OT_PARSE_TRACER_DEF(     OPTION, 0, 0, 2, 2, "option",      " { disabled | dontlog-normal | hard-errors }")                                      \
+	FLT_OT_PARSE_TRACER_DEF(DEBUG_LEVEL, 0, 0, 2, 2, "debug-level", " <value>")
+
+#define FLT_OT_PARSE_GROUP_DEFINES                                        \
+	FLT_OT_PARSE_GROUP_DEF(    ID, 0, 1, 2, 2, "ot-group", " <name>") \
+	FLT_OT_PARSE_GROUP_DEF(SCOPES, 0, 0, 2, 0, "scopes",   " <name> ...")
+
+#define FLT_OT_PARSE_SCOPE_DEFINES                                                                                    \
+	FLT_OT_PARSE_SCOPE_DEF(     ID, 0, 1, 2, 2, "ot-scope", " <name>")                                            \
+	FLT_OT_PARSE_SCOPE_DEF(   SPAN, 0, 0, 2, 5, "span",     " <name> [<reference>] [root]")                       \
+	FLT_OT_PARSE_SCOPE_DEF(    TAG, 1, 0, 3, 0, "tag",      " <name> <sample> ...")                               \
+	FLT_OT_PARSE_SCOPE_DEF(    LOG, 1, 0, 3, 0, "log",      " <name> <sample> ...")                               \
+	FLT_OT_PARSE_SCOPE_DEF(BAGGAGE, 1, 4, 3, 0, "baggage",  " <name> <sample> ...")                               \
+	FLT_OT_PARSE_SCOPE_DEF( INJECT, 1, 3, 2, 4, "inject",   " <name-prefix> [use-vars] [use-headers]")            \
+	FLT_OT_PARSE_SCOPE_DEF(EXTRACT, 0, 3, 2, 3, "extract",  " <name-prefix> [use-vars | use-headers]")            \
+	FLT_OT_PARSE_SCOPE_DEF( FINISH, 0, 0, 2, 0, "finish",   " <name> ...")                                        \
+	FLT_OT_PARSE_SCOPE_DEF(    ACL, 0, 1, 3, 0, "acl",      " <name> <criterion> [flags] [operator] <value> ...") \
+	FLT_OT_PARSE_SCOPE_DEF(  EVENT, 0, 0, 2, 0, "event",    " <name> [{ if | unless } <condition>]")
+
+enum FLT_OT_PARSE_TRACER_enum {
+#define FLT_OT_PARSE_TRACER_DEF(a,b,c,d,e,f,g)   FLT_OT_PARSE_TRACER_##a,
+	FLT_OT_PARSE_TRACER_DEFINES
+#undef FLT_OT_PARSE_TRACER_DEF
+};
+
+enum FLT_OT_PARSE_GROUP_enum {
+#define FLT_OT_PARSE_GROUP_DEF(a,b,c,d,e,f,g)   FLT_OT_PARSE_GROUP_##a,
+	FLT_OT_PARSE_GROUP_DEFINES
+#undef FLT_OT_PARSE_GROUP_DEF
+};
+
+enum FLT_OT_PARSE_SCOPE_enum {
+#define FLT_OT_PARSE_SCOPE_DEF(a,b,c,d,e,f,g)   FLT_OT_PARSE_SCOPE_##a,
+	FLT_OT_PARSE_SCOPE_DEFINES
+#undef FLT_OT_PARSE_SCOPE_DEF
+};
+
+enum FLT_OT_CTX_USE_enum {
+	FLT_OT_CTX_USE_VARS    = 1 << 0,
+	FLT_OT_CTX_USE_HEADERS = 1 << 1,
+};
+
+struct flt_ot_parse_data {
+	int         keyword;       /* Keyword index. */
+	bool        flag_check_id; /* Whether the group ID must be defined for the keyword. */
+	int         check_name;    /* Checking allowed characters in the name. */
+	int         args_min;      /* The minimum number of arguments required. */
+	int         args_max;      /* The maximum number of arguments allowed. */
+	const char *name;          /* Keyword name. */
+	const char *usage;         /* Usage text to be printed in case of an error. */
+};
+
+#define FLT_OT_PARSE_WARNING(f, ...) \
+	ha_warning("parsing [%s:%d] : " FLT_OT_FMT_TYPE FLT_OT_FMT_NAME "'" f "'\n", ##__VA_ARGS__);
+#define FLT_OT_PARSE_ALERT(f, ...)                                                                         \
+	do {                                                                                               \
+		ha_alert("parsing [%s:%d] : " FLT_OT_FMT_TYPE FLT_OT_FMT_NAME "'" f "'\n", ##__VA_ARGS__); \
+                                                                                                           \
+		retval |= ERR_ABORT | ERR_ALERT;                                                           \
+	} while (0)
+#define FLT_OT_POST_PARSE_ALERT(f, ...) \
+	FLT_OT_PARSE_ALERT(f, flt_ot_current_config->cfg_file, ##__VA_ARGS__)
+
+#define FLT_OT_PARSE_ERR(e,f, ...)                              \
+	do {                                                    \
+		if (*(e) == NULL)                               \
+			(void)memprintf((e), f, ##__VA_ARGS__); \
+                                                                \
+		retval |= ERR_ABORT | ERR_ALERT;                \
+	} while (0)
+#define FLT_OT_PARSE_IFERR_ALERT()                            \
+	do {                                                  \
+		if (err == NULL)                              \
+			break;                                \
+                                                              \
+		FLT_OT_PARSE_ALERT("%s", file, linenum, err); \
+		FLT_OT_ERR_FREE(err);                         \
+	} while (0)
+
+#endif /* _OPENTRACING_PARSER_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/pool.h b/addons/ot/include/pool.h
new file mode 100644
index 0000000..df72c84
--- /dev/null
+++ b/addons/ot/include/pool.h
@@ -0,0 +1,39 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_POOL_H_
+#define _OPENTRACING_POOL_H_
+
+void          *flt_ot_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err);
+void          *flt_ot_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err);
+void           flt_ot_pool_free(struct pool_head *pool, void **ptr);
+
+struct buffer *flt_ot_trash_alloc(bool flag_clear, char **err);
+void           flt_ot_trash_free(struct buffer **ptr);
+
+#endif /* _OPENTRACING_POOL_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/scope.h b/addons/ot/include/scope.h
new file mode 100644
index 0000000..7b3f265
--- /dev/null
+++ b/addons/ot/include/scope.h
@@ -0,0 +1,141 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_SCOPE_H_
+#define _OPENTRACING_SCOPE_H_
+
+#define FLT_OT_SCOPE_SPAN_FINISH_REQ   "*req*"
+#define FLT_OT_SCOPE_SPAN_FINISH_RES   "*res*"
+#define FLT_OT_SCOPE_SPAN_FINISH_ALL   "*"
+
+#define FLT_OT_RT_CTX(a)               ((struct flt_ot_runtime_context *)(a))
+
+#define FLT_OT_DBG_SCOPE_SPAN(f,a)                                         \
+	FLT_OT_DBG(3, "%s%p:{ '%s' %zu %u %hhu %p %d %p %p }",             \
+	           (f), (a), FLT_OT_STR_HDR_ARGS(a, id), (a)->smp_opt_dir, \
+	           (a)->flag_finish, (a)->span, (a)->ref_type, (a)->ref_span, (a)->ref_ctx)
+
+#define FLT_OT_DBG_SCOPE_CONTEXT(f,a)                                      \
+	FLT_OT_DBG(3, "%s%p:{ '%s' %zu %u %hhu %p }",                      \
+	           (f), (a), FLT_OT_STR_HDR_ARGS(a, id), (a)->smp_opt_dir, \
+	           (a)->flag_finish, (a)->context)
+
+#define FLT_OT_DBG_SCOPE_DATA(f,a)               \
+	FLT_OT_DBG(3, "%s%p:{ %p %d %p %p %d }", \
+	           (f), (a), (a)->tags, (a)->num_tags, (a)->baggage, (a)->log_fields, (a)->num_log_fields)
+
+#define FLT_OT_DBG_RUNTIME_CONTEXT(f,a)                                                                                    \
+	FLT_OT_DBG(3, "%s%p:{ %p %p { %016" PRIx64 " %016" PRIx64 " '%s' } %hhu %hhu 0x%02hhx 0x%08x %s %s }",             \
+	           (f), (a), (a)->stream, (a)->filter, (a)->uuid.u64[0], (a)->uuid.u64[1], (a)->uuid.s, (a)->flag_harderr, \
+	           (a)->flag_disabled, (a)->logging, (a)->analyzers, flt_ot_list_debug(&((a)->spans)),                     \
+	           flt_ot_list_debug(&((a)->contexts)))
+
+#define FLT_OT_CONST_STR_HDR(a)      \
+	struct {                     \
+		const char *a;       \
+		size_t      a##_len; \
+	}
+
+
+struct flt_ot_scope_data {
+	struct otc_tag        tags[FLT_OT_MAXTAGS];         /* Defined tags. */
+	int                   num_tags;                     /* The number of tags used. */
+	struct otc_text_map  *baggage;                      /* Defined baggage. */
+	struct otc_log_field  log_fields[OTC_MAXLOGFIELDS]; /* Defined logs. */
+	int                   num_log_fields;               /* The number of log fields used. */
+};
+
+/* flt_ot_runtime_context->spans */
+struct flt_ot_scope_span {
+	FLT_OT_CONST_STR_HDR(id);               /* The span operation name/len. */
+	uint                       smp_opt_dir; /* SMP_OPT_DIR_RE(Q|S) */
+	bool                       flag_finish; /* Whether the span is marked for completion. */
+	struct otc_span           *span;        /* The current span. */
+	otc_span_reference_type_t  ref_type;    /* Span reference type. */
+	struct otc_span           *ref_span;    /* Span to which the current span refers. */
+	struct otc_span_context   *ref_ctx;     /* Span context to which the current span refers. */
+	struct list                list;        /* Used to chain this structure. */
+};
+
+/* flt_ot_runtime_context->contexts */
+struct flt_ot_scope_context {
+	FLT_OT_CONST_STR_HDR(id);             /* The span context name/len. */
+	uint                     smp_opt_dir; /* SMP_OPT_DIR_RE(Q|S) */
+	bool                     flag_finish; /* Whether the span context is marked for completion. */
+	struct otc_span_context *context;     /* The current span context. */
+	struct list              list;        /* Used to chain this structure. */
+};
+
+struct flt_ot_uuid {
+	union {
+		uint64_t u64[2];
+		uint8_t  u8[16];
+		struct {
+			uint32_t time_low;
+			uint16_t time_mid;
+			uint16_t time_hi_and_version;
+			uint16_t clock_seq;
+			uint64_t node : 48;
+		} __attribute__((packed));
+	};
+	char s[40];
+};
+
+/* The runtime filter context attached to a stream. */
+struct flt_ot_runtime_context {
+	struct stream      *stream;        /* The stream to which the filter is attached. */
+	struct filter      *filter;        /* The OpenTracing filter. */
+	struct flt_ot_uuid  uuid;          /* Randomly generated UUID. */
+	bool                flag_harderr;  /* [0 1] */
+	bool                flag_disabled; /* [0 1] */
+	uint8_t             logging;       /* [0 1 3] */
+	uint                analyzers;     /* Executed channel analyzers. */
+	struct list         spans;         /* The scope spans. */
+	struct list         contexts;      /* The scope contexts. */
+};
+
+
+#ifndef DEBUG_OT
+#  define flt_ot_pools_info()   while (0)
+#else
+void                           flt_ot_pools_info(void);
+#endif
+struct flt_ot_runtime_context *flt_ot_runtime_context_init(struct stream *s, struct filter *f, char **err);
+void                           flt_ot_runtime_context_free(struct filter *f);
+
+struct flt_ot_scope_span      *flt_ot_scope_span_init(struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len, otc_span_reference_type_t ref_type, const char *ref_id, size_t ref_id_len, uint dir, char **err);
+void                           flt_ot_scope_span_free(struct flt_ot_scope_span **ptr);
+struct flt_ot_scope_context   *flt_ot_scope_context_init(struct flt_ot_runtime_context *rt_ctx, struct otc_tracer *tracer, const char *id, size_t id_len, const struct otc_text_map *text_map, uint dir, char **err);
+void                           flt_ot_scope_context_free(struct flt_ot_scope_context **ptr);
+void                           flt_ot_scope_data_free(struct flt_ot_scope_data *ptr);
+
+int                            flt_ot_scope_finish_mark(const struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len);
+void                           flt_ot_scope_finish_marked(const struct flt_ot_runtime_context *rt_ctx, const struct timespec *ts_finish);
+void                           flt_ot_scope_free_unused(struct flt_ot_runtime_context *rt_ctx, struct channel *chn);
+
+#endif /* _OPENTRACING_SCOPE_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/util.h b/addons/ot/include/util.h
new file mode 100644
index 0000000..776ddd2
--- /dev/null
+++ b/addons/ot/include/util.h
@@ -0,0 +1,109 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_UTIL_H_
+#define _OPENTRACING_UTIL_H_
+
+#define HTTP_METH_STR_OPTIONS   "OPTIONS"
+#define HTTP_METH_STR_GET       "GET"
+#define HTTP_METH_STR_HEAD      "HEAD"
+#define HTTP_METH_STR_POST      "POST"
+#define HTTP_METH_STR_PUT       "PUT"
+#define HTTP_METH_STR_DELETE    "DELETE"
+#define HTTP_METH_STR_TRACE     "TRACE"
+#define HTTP_METH_STR_CONNECT   "CONNECT"
+
+/* Defined in include/haproxy/channel-t.h. */
+#define FLT_OT_AN_DEFINES                     \
+	FLT_OT_AN_DEF(AN_REQ_INSPECT_FE)      \
+	FLT_OT_AN_DEF(AN_REQ_WAIT_HTTP)       \
+	FLT_OT_AN_DEF(AN_REQ_HTTP_BODY)       \
+	FLT_OT_AN_DEF(AN_REQ_HTTP_PROCESS_FE) \
+	FLT_OT_AN_DEF(AN_REQ_SWITCHING_RULES) \
+	FLT_OT_AN_DEF(AN_REQ_INSPECT_BE)      \
+	FLT_OT_AN_DEF(AN_REQ_HTTP_PROCESS_BE) \
+	FLT_OT_AN_DEF(AN_REQ_HTTP_TARPIT)     \
+	FLT_OT_AN_DEF(AN_REQ_SRV_RULES)       \
+	FLT_OT_AN_DEF(AN_REQ_HTTP_INNER)      \
+	FLT_OT_AN_DEF(AN_REQ_PRST_RDP_COOKIE) \
+	FLT_OT_AN_DEF(AN_REQ_STICKING_RULES)  \
+	FLT_OT_AN_DEF(AN_REQ_HTTP_XFER_BODY)  \
+	FLT_OT_AN_DEF(AN_REQ_WAIT_CLI)        \
+	FLT_OT_AN_DEF(AN_RES_INSPECT)         \
+	FLT_OT_AN_DEF(AN_RES_WAIT_HTTP)       \
+	FLT_OT_AN_DEF(AN_RES_STORE_RULES)     \
+	FLT_OT_AN_DEF(AN_RES_HTTP_PROCESS_BE) \
+	FLT_OT_AN_DEF(AN_RES_HTTP_PROCESS_FE) \
+	FLT_OT_AN_DEF(AN_RES_HTTP_XFER_BODY)  \
+	FLT_OT_AN_DEF(AN_RES_WAIT_CLI)
+
+#define FLT_OT_PROXIES_LIST_START()                                             \
+	do {                                                                    \
+		struct flt_conf *fconf;                                         \
+		struct proxy    *px;                                            \
+                                                                                \
+		for (px = proxies_list; px != NULL; px = px->next)              \
+			list_for_each_entry(fconf, &(px->filter_configs), list) \
+				if (fconf->id == ot_flt_id) {                   \
+					struct flt_ot_conf *conf = fconf->conf;
+#define FLT_OT_PROXIES_LIST_END() \
+				} \
+	} while (0)
+
+#ifdef DEBUG_OT
+#  define FLT_OT_ARGS_DUMP()   do { if (flt_ot_debug.level & (1 << 2)) flt_ot_args_dump(args); } while (0)
+#else
+#  define FLT_OT_ARGS_DUMP()   while (0)
+#endif
+
+
+#ifndef DEBUG_OT
+#  define flt_ot_filters_dump()   while (0)
+#else
+void        flt_ot_args_dump(char **args);
+void        flt_ot_filters_dump(void);
+const char *flt_ot_chn_label(const struct channel *chn);
+const char *flt_ot_pr_mode(const struct stream *s);
+const char *flt_ot_stream_pos(const struct stream *s);
+const char *flt_ot_type(const struct filter *f);
+const char *flt_ot_analyzer(uint an_bit);
+const char *flt_ot_str_hex(const void *data, size_t size);
+const char *flt_ot_str_ctrl(const void *data, size_t size);
+const char *flt_ot_list_debug(const struct list *head);
+#endif
+
+ssize_t     flt_ot_chunk_add(struct buffer *chk, const void *src, size_t n, char **err);
+int         flt_ot_args_count(char **args);
+void        flt_ot_args_to_str(char **args, int idx, char **str);
+double      flt_ot_strtod(const char *nptr, double limit_min, double limit_max, char **err);
+int64_t     flt_ot_strtoll(const char *nptr, int64_t limit_min, int64_t limit_max, char **err);
+int         flt_ot_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err);
+int         flt_ot_sample_to_value(const char *key, const struct sample_data *data, struct otc_value *value, char **err);
+int         flt_ot_sample_add(struct stream *s, uint dir, struct flt_ot_conf_sample *sample, struct flt_ot_scope_data *data, int type, char **err);
+
+#endif /* _OPENTRACING_UTIL_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/include/vars.h b/addons/ot/include/vars.h
new file mode 100644
index 0000000..5ce8879
--- /dev/null
+++ b/addons/ot/include/vars.h
@@ -0,0 +1,49 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#ifndef _OPENTRACING_VARS_H_
+#define _OPENTRACING_VARS_H_
+
+#define FLT_OT_VARS_SCOPE       "txn"
+#define FLT_OT_VAR_CHAR_DASH    'D'
+#define FLT_OT_VAR_CHAR_SPACE   'S'
+
+
+#ifndef DEBUG_OT
+#  define flt_ot_vars_dump(...)   while (0)
+#else
+void                 flt_ot_vars_dump(struct stream *s);
+#endif
+int                  flt_ot_var_register(const char *scope, const char *prefix, const char *name, char **err);
+int                  flt_ot_var_set(struct stream *s, const char *scope, const char *prefix, const char *name, const char *value, uint opt, char **err);
+int                  flt_ot_var_unset(struct stream *s, const char *scope, const char *prefix, const char *name, uint opt, char **err);
+int                  flt_ot_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err);
+int                  flt_ot_var_get(struct stream *s, const char *scope, const char *prefix, const char *name, char **value, uint opt, char **err);
+struct otc_text_map *flt_ot_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err);
+
+#endif /* _OPENTRACING_VARS_H_ */
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/cli.c b/addons/ot/src/cli.c
new file mode 100644
index 0000000..529c687
--- /dev/null
+++ b/addons/ot/src/cli.c
@@ -0,0 +1,395 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+/***
+ * NAME
+ *   flt_ot_cli_set_msg -
+ *
+ * ARGUMENTS
+ *   appctx    -
+ *   err       -
+ *   msg       -
+ *   cli_state -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void cmn_cli_set_msg(struct appctx *appctx, char *err, char *msg, int cli_state)
+{
+	FLT_OT_FUNC("%p, %p, %p, %d", appctx, err, msg, cli_state);
+
+	if ((appctx == NULL) || ((err == NULL) && (msg == NULL)))
+		FLT_OT_RETURN();
+
+	appctx->ctx.cli.err = (err == NULL) ? msg : err;
+	appctx->st0         = (appctx->ctx.cli.err == NULL) ? CLI_ST_PROMPT : cli_state;
+
+	FLT_OT_DBG(1, "err(%d): \"%s\"", appctx->st0, appctx->ctx.cli.err);
+
+	FLT_OT_RETURN();
+}
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_cli_parse_debug -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   payload -
+ *   appctx  -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_cli_parse_debug(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char    *err = NULL, *msg = NULL;
+	uint8_t  value;
+	int      retval = 0;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
+
+	FLT_OT_ARGS_DUMP();
+
+	if (FLT_OT_ARG_ISVALID(2)) {
+		value = flt_ot_strtoll(args[2], 0, 255, &err);
+		if (err == NULL) {
+			_HA_ATOMIC_STORE(&(flt_ot_debug.level), value);
+
+			(void)memprintf(&msg, FLT_OT_CLI_CMD " : debug level set to %hhu", value);
+		} else {
+			retval = 1;
+		}
+	} else {
+		value = _HA_ATOMIC_LOAD(&(flt_ot_debug.level));
+
+		(void)memprintf(&msg, FLT_OT_CLI_CMD " : current debug level is %hhu", value);
+	}
+
+	cmn_cli_set_msg(appctx, err, msg, CLI_ST_PRINT_FREE);
+
+	FLT_OT_RETURN(retval);
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_cli_parse_disabled -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   payload -
+ *   appctx  -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_cli_parse_disabled(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char *msg = NULL;
+	bool  value = (uintptr_t)private;
+	int   retval = 0;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
+
+	FLT_OT_ARGS_DUMP();
+
+	FLT_OT_PROXIES_LIST_START() {
+		_HA_ATOMIC_STORE(&(conf->tracer->flag_disabled), value);
+
+		(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : filter %sabled", FLT_OT_CLI_MSG_CAT(msg), value ? "dis" : "en");
+	} FLT_OT_PROXIES_LIST_END();
+
+	cmn_cli_set_msg(appctx, NULL, msg, CLI_ST_PRINT_FREE);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_cli_parse_option -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   payload -
+ *   appctx  -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_cli_parse_option(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char *msg = NULL;
+	bool  value = (uintptr_t)private;
+	int   retval = 0;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
+
+	FLT_OT_ARGS_DUMP();
+
+	FLT_OT_PROXIES_LIST_START() {
+		_HA_ATOMIC_STORE(&(conf->tracer->flag_harderr), value);
+
+		(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : filter set %s-errors", FLT_OT_CLI_MSG_CAT(msg), value ? "hard" : "soft");
+	} FLT_OT_PROXIES_LIST_END();
+
+	cmn_cli_set_msg(appctx, NULL, msg, CLI_ST_PRINT_FREE);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_cli_parse_logging -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   payload -
+ *   appctx  -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_cli_parse_logging(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char    *err = NULL, *msg = NULL;
+	uint8_t  value;
+	int      retval = 0;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
+
+	FLT_OT_ARGS_DUMP();
+
+	if (FLT_OT_ARG_ISVALID(2)) {
+		if (strcasecmp(args[2], FLT_OT_CLI_LOGGING_OFF) == 0) {
+			value = FLT_OT_LOGGING_OFF;
+		}
+		else if (strcasecmp(args[2], FLT_OT_CLI_LOGGING_ON) == 0) {
+			value = FLT_OT_LOGGING_ON;
+		}
+		else if (strcasecmp(args[2], FLT_OT_CLI_LOGGING_NOLOGNORM) == 0) {
+			value = FLT_OT_LOGGING_ON | FLT_OT_LOGGING_NOLOGNORM;
+		}
+		else {
+			(void)memprintf(&err, "'%s' : invalid value, use <" FLT_OT_CLI_LOGGING_OFF " | " FLT_OT_CLI_LOGGING_ON " | " FLT_OT_CLI_LOGGING_NOLOGNORM ">", args[2]);
+
+			retval = 1;
+		}
+
+		if (retval == 0) {
+			FLT_OT_PROXIES_LIST_START() {
+				_HA_ATOMIC_STORE(&(conf->tracer->logging), value);
+
+				(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : logging is %s", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_CLI_LOGGING_STATE(value));
+			} FLT_OT_PROXIES_LIST_END();
+		}
+	} else {
+		FLT_OT_PROXIES_LIST_START() {
+			value = _HA_ATOMIC_LOAD(&(conf->tracer->logging));
+
+			(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : logging is currently %s", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_CLI_LOGGING_STATE(value));
+		} FLT_OT_PROXIES_LIST_END();
+	}
+
+	cmn_cli_set_msg(appctx, err, msg, CLI_ST_PRINT_FREE);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_cli_parse_rate -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   payload -
+ *   appctx  -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_cli_parse_rate(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	char     *err = NULL, *msg = NULL;
+	uint32_t  value;
+	int       retval = 0;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
+
+	FLT_OT_ARGS_DUMP();
+
+	if (FLT_OT_ARG_ISVALID(2)) {
+		value = FLT_OT_FLOAT_U32(flt_ot_strtod(args[2], 0.0, FLT_OT_RATE_LIMIT_MAX, &err), FLT_OT_RATE_LIMIT_MAX);
+		if (err == NULL) {
+			FLT_OT_PROXIES_LIST_START() {
+				_HA_ATOMIC_STORE(&(conf->tracer->rate_limit), value);
+
+				(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : rate limit set to %.2f", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_U32_FLOAT(value, FLT_OT_RATE_LIMIT_MAX));
+			} FLT_OT_PROXIES_LIST_END();
+		} else {
+			retval = 1;
+		}
+	} else {
+		FLT_OT_PROXIES_LIST_START() {
+			value = _HA_ATOMIC_LOAD(&(conf->tracer->rate_limit));
+
+			(void)memprintf(&msg, "%s%s" FLT_OT_CLI_CMD " : current rate limit is %.2f", FLT_OT_CLI_MSG_CAT(msg), FLT_OT_U32_FLOAT(value, FLT_OT_RATE_LIMIT_MAX));
+		} FLT_OT_PROXIES_LIST_END();
+	}
+
+	cmn_cli_set_msg(appctx, err, msg, CLI_ST_PRINT_FREE);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_cli_parse_status -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   payload -
+ *   appctx  -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_cli_parse_status(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	const char *nl = "";
+	char       *msg = NULL;
+	int         retval = 0;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p", args, payload, appctx, private);
+
+	FLT_OT_ARGS_DUMP();
+	flt_ot_filters_dump();
+
+	(void)memprintf(&msg, " " FLT_OT_OPT_NAME " filter status\n" FLT_OT_STR_DASH_78);
+#ifdef DEBUG_OT
+	(void)memprintf(&msg, "%s\n   debug level: 0x%02hhx\n", msg, flt_ot_debug.level);
+#endif
+
+	FLT_OT_PROXIES_LIST_START() {
+		(void)memprintf(&msg, "%s\n%s   filter %s\n", msg, nl, conf->id);
+		(void)memprintf(&msg, "%s     configuration: %s\n", msg, conf->cfg_file);
+		(void)memprintf(&msg, "%s     disable count: %" PRIu64 " %" PRIu64 "\n\n", msg, conf->cnt.disabled[0], conf->cnt.disabled[1]);
+		(void)memprintf(&msg, "%s     tracer %s\n", msg, conf->tracer->id);
+		(void)memprintf(&msg, "%s       configuration: %s\n", msg, conf->tracer->config);
+		(void)memprintf(&msg, "%s       plugin:        %s\n", msg, conf->tracer->plugin);
+		(void)memprintf(&msg, "%s       rate limit:    %.2f %%\n", msg, FLT_OT_U32_FLOAT(conf->tracer->rate_limit, FLT_OT_RATE_LIMIT_MAX));
+		(void)memprintf(&msg, "%s       hard errors:   %s\n", msg, FLT_OT_STR_FLAG_YN(conf->tracer->flag_harderr));
+		(void)memprintf(&msg, "%s       disabled:      %s\n", msg, FLT_OT_STR_FLAG_YN(conf->tracer->flag_disabled));
+		(void)memprintf(&msg, "%s       logging:       %s\n", msg, FLT_OT_CLI_LOGGING_STATE(conf->tracer->logging));
+		(void)memprintf(&msg, "%s       analyzers:     %08x", msg, conf->tracer->analyzers);
+
+		nl = "\n";
+	} FLT_OT_PROXIES_LIST_END();
+
+	cmn_cli_set_msg(appctx, NULL, msg, CLI_ST_PRINT_FREE);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+static struct cli_kw_list cli_kws = { { }, {
+#ifdef DEBUG_OT
+	{ { FLT_OT_CLI_CMD, "debug", NULL }, FLT_OT_CLI_CMD " debug [level]   : set the OT filter debug level (default: get current debug level)", flt_ot_cli_parse_debug, NULL, NULL, NULL, 0 },
+#endif
+	{ { FLT_OT_CLI_CMD, "disable", NULL }, FLT_OT_CLI_CMD " disable         : disable the OT filter", flt_ot_cli_parse_disabled, NULL, NULL, (void *)1, 0 },
+	{ { FLT_OT_CLI_CMD, "enable", NULL }, FLT_OT_CLI_CMD " enable          : enable the OT filter", flt_ot_cli_parse_disabled, NULL, NULL, (void *)0, 0 },
+	{ { FLT_OT_CLI_CMD, "soft-errors", NULL }, FLT_OT_CLI_CMD " soft-errors     : turning off hard-errors mode", flt_ot_cli_parse_option, NULL, NULL, (void *)0, 0 },
+	{ { FLT_OT_CLI_CMD, "hard-errors", NULL }, FLT_OT_CLI_CMD " hard-errors     : enabling hard-errors mode", flt_ot_cli_parse_option, NULL, NULL, (void *)1, 0 },
+	{ { FLT_OT_CLI_CMD, "logging",  NULL }, FLT_OT_CLI_CMD " logging [state] : set logging state (default: get current logging state)", flt_ot_cli_parse_logging, NULL, NULL, NULL, 0 },
+	{ { FLT_OT_CLI_CMD, "rate", NULL }, FLT_OT_CLI_CMD " rate [value]    : set the rate limit (default: get current rate value)", flt_ot_cli_parse_rate, NULL, NULL, NULL, 0 },
+	{ { FLT_OT_CLI_CMD, "status", NULL }, FLT_OT_CLI_CMD " status          : show the OT filter status", flt_ot_cli_parse_status, NULL, NULL, NULL, 0 },
+	{ /* END */ }
+}};
+
+
+/***
+ * NAME
+ *   flt_ot_cli_init -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_cli_init(void)
+{
+	FLT_OT_FUNC("");
+
+	/* Register CLI keywords. */
+	cli_register_kw(&cli_kws);
+
+	FLT_OT_RETURN();
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/conf.c b/addons/ot/src/conf.c
new file mode 100644
index 0000000..626d418
--- /dev/null
+++ b/addons/ot/src/conf.c
@@ -0,0 +1,766 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+/***
+ * NAME
+ *   flt_ot_conf_hdr_init -
+ *
+ * ARGUMENTS
+ *   size    -
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static void *flt_ot_conf_hdr_init(size_t size, const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_hdr *retptr = NULL, *ptr;
+
+	FLT_OT_FUNC("%zu, \"%s\", %d, %p, %p:%p", size, id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	if (head != NULL)
+		list_for_each_entry(ptr, head, list)
+			if (strcmp(ptr->id, id) == 0) {
+				FLT_OT_ERR("'%s' : already defined", id);
+
+				FLT_OT_RETURN(retptr);
+			}
+
+	retptr = FLT_OT_CALLOC(1, size);
+	if (retptr != NULL) {
+		retptr->id_len = strlen(id);
+		if (retptr->id_len >= FLT_OT_ID_MAXLEN)
+			FLT_OT_ERR("'%s' : name too long", id);
+		else
+			retptr->id = FLT_OT_STRDUP(id);
+
+		if (retptr->id == NULL)
+			FLT_OT_FREE_CLEAR(retptr);
+	}
+
+	if (retptr != NULL) {
+		retptr->cfg_line = linenum;
+
+		if (head != NULL)
+			LIST_ADDQ(head, &(retptr->list));
+	} else {
+		FLT_OT_ERR("out of memory");
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_ph_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_ph *flt_ot_conf_ph_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_ph *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr != NULL)
+		FLT_OT_DBG_CONF_PH("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_ph_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_ph_free(struct flt_ot_conf_ph **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_PH("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_sample_expr_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_sample_expr *flt_ot_conf_sample_expr_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_sample_expr *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr != NULL)
+		FLT_OT_DBG_CONF_SAMPLE_EXPR("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_sample_expr_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_sample_expr_free(struct flt_ot_conf_sample_expr **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_SAMPLE_EXPR("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->value);
+	release_sample_expr((*ptr)->expr);
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_sample_init -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_sample *flt_ot_conf_sample_init(char **args, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_sample *retptr;
+
+	FLT_OT_FUNC("%p, %d, %p, %p:%p", args, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), args[1], linenum, head, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	flt_ot_args_to_str(args, 2, &(retptr->value));
+	if (retptr->value == NULL) {
+		FLT_OT_FREE_CLEAR(retptr);
+
+		FLT_OT_RETURN(retptr);
+	}
+
+	retptr->num_exprs = flt_ot_args_count(args) - 2;
+	LIST_INIT(&(retptr->exprs));
+
+	FLT_OT_DBG_CONF_SAMPLE("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_sample_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_sample_free(struct flt_ot_conf_sample **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_SAMPLE("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->key);
+	FLT_OT_FREE((*ptr)->value);
+	FLT_OT_LIST_DESTROY(sample_expr, &((*ptr)->exprs));
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_str_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_str *flt_ot_conf_str_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_str *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr != NULL)
+		FLT_OT_DBG_CONF_STR("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_str_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_str_free(struct flt_ot_conf_str **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_STR("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->str);
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_context_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_context *flt_ot_conf_context_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_context *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr != NULL)
+		FLT_OT_DBG_CONF_CONTEXT("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_context_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_context_free(struct flt_ot_conf_context **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_CONTEXT("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_span_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_span *flt_ot_conf_span_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_span *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	LIST_INIT(&(retptr->tags));
+	LIST_INIT(&(retptr->logs));
+	LIST_INIT(&(retptr->baggages));
+
+	FLT_OT_DBG_CONF_SPAN("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_span_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_span_free(struct flt_ot_conf_span **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_SPAN("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	FLT_OT_FREE((*ptr)->ref_id);
+	FLT_OT_FREE((*ptr)->ctx_id);
+	FLT_OT_LIST_DESTROY(sample, &((*ptr)->tags));
+	FLT_OT_LIST_DESTROY(sample, &((*ptr)->logs));
+	FLT_OT_LIST_DESTROY(sample, &((*ptr)->baggages));
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_scope_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_scope *flt_ot_conf_scope_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_scope *retptr = NULL;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	LIST_INIT(&(retptr->acls));
+	LIST_INIT(&(retptr->contexts));
+	LIST_INIT(&(retptr->spans));
+	LIST_INIT(&(retptr->finish));
+
+	FLT_OT_DBG_CONF_SCOPE("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+/***
+ * NAME
+ *   flt_ot_conf_scope_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_scope_free(struct flt_ot_conf_scope **ptr)
+{
+	struct acl *acl, *aclback;
+
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_SCOPE("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	list_for_each_entry_safe(acl, aclback, &((*ptr)->acls), list) {
+		prune_acl(acl);
+		FLT_OT_LIST_DEL(&(acl->list));
+		FLT_OT_FREE(acl);
+	}
+	if ((*ptr)->cond != NULL) {
+		prune_acl_cond((*ptr)->cond);
+		FLT_OT_FREE((*ptr)->cond);
+	}
+	FLT_OT_LIST_DESTROY(context, &((*ptr)->contexts));
+	FLT_OT_LIST_DESTROY(span, &((*ptr)->spans));
+	FLT_OT_LIST_DESTROY(str, &((*ptr)->finish));
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_group_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_group *flt_ot_conf_group_init(const char *id, int linenum, struct list *head, char **err)
+{
+	struct flt_ot_conf_group *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p:%p", id, linenum, head, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, head, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	LIST_INIT(&(retptr->ph_scopes));
+
+	FLT_OT_DBG_CONF_GROUP("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_group_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_group_free(struct flt_ot_conf_group **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_GROUP("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	FLT_OT_LIST_DESTROY(ph_scope, &((*ptr)->ph_scopes));
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_tracer_init -
+ *
+ * ARGUMENTS
+ *   id      -
+ *   linenum -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf_tracer *flt_ot_conf_tracer_init(const char *id, int linenum, char **err)
+{
+	struct flt_ot_conf_tracer *retptr;
+
+	FLT_OT_FUNC("\"%s\", %d, %p:%p", id, linenum, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_conf_hdr_init(sizeof(*retptr), id, linenum, NULL, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr->rate_limit = FLT_OT_FLOAT_U32(FLT_OT_RATE_LIMIT_MAX, FLT_OT_RATE_LIMIT_MAX);
+	init_new_proxy(&(retptr->proxy_log));
+	LIST_INIT(&(retptr->acls));
+	LIST_INIT(&(retptr->ph_groups));
+	LIST_INIT(&(retptr->ph_scopes));
+
+	FLT_OT_DBG_CONF_TRACER("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_tracer_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_tracer_free(struct flt_ot_conf_tracer **ptr)
+{
+	struct acl    *acl, *aclback;
+	struct logsrv *logsrv, *logsrvback;
+
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF_TRACER("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	FLT_OT_FREE((*ptr)->config);
+	FLT_OT_FREE((*ptr)->plugin);
+	FLT_OT_DBG(2, "- deleting acls list %s", flt_ot_list_debug(&((*ptr)->acls)));
+	list_for_each_entry_safe(acl, aclback, &((*ptr)->acls), list) {
+		prune_acl(acl);
+		FLT_OT_LIST_DEL(&(acl->list));
+		FLT_OT_FREE(acl);
+	}
+	FLT_OT_DBG(2, "- deleting proxy_log.logsrvs list %s", flt_ot_list_debug(&((*ptr)->proxy_log.logsrvs)));
+	list_for_each_entry_safe(logsrv, logsrvback, &((*ptr)->proxy_log.logsrvs), list) {
+		LIST_DEL(&(logsrv->list));
+		FLT_OT_FREE(logsrv);
+	}
+	FLT_OT_LIST_DESTROY(ph_group, &((*ptr)->ph_groups));
+	FLT_OT_LIST_DESTROY(ph_scope, &((*ptr)->ph_scopes));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_init -
+ *
+ * ARGUMENTS
+ *   px -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_conf *flt_ot_conf_init(struct proxy *px)
+{
+	struct flt_ot_conf *retptr;
+
+	FLT_OT_FUNC("%p", px);
+
+	retptr = FLT_OT_CALLOC(1, sizeof(*retptr));
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr->proxy = px;
+	LIST_INIT(&(retptr->groups));
+	LIST_INIT(&(retptr->scopes));
+
+	FLT_OT_DBG_CONF("- init ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_conf_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_conf_free(struct flt_ot_conf **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_CONF("- free ", *ptr);
+
+	FLT_OT_FREE((*ptr)->id);
+	FLT_OT_FREE((*ptr)->cfg_file);
+	flt_ot_conf_tracer_free(&((*ptr)->tracer));
+	FLT_OT_LIST_DESTROY(group, &((*ptr)->groups));
+	FLT_OT_LIST_DESTROY(scope, &((*ptr)->scopes));
+	FLT_OT_FREE_CLEAR(*ptr);
+
+	FLT_OT_RETURN();
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/event.c b/addons/ot/src/event.c
new file mode 100644
index 0000000..90b5828
--- /dev/null
+++ b/addons/ot/src/event.c
@@ -0,0 +1,332 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+#define FLT_OT_EVENT_DEF(a,b,c,d,e,f)   { AN_##b##_##a, SMP_OPT_DIR_##b, SMP_VAL_FE_##c, SMP_VAL_BE_##d, e, f },
+const struct flt_ot_event_data flt_ot_event_data[FLT_OT_EVENT_MAX] = { FLT_OT_EVENT_DEFINES };
+#undef FLT_OT_EVENT_DEF
+
+
+/***
+ * NAME
+ *   flt_ot_scope_run_span -
+ *
+ * ARGUMENTS
+ *   s         -
+ *   f         -
+ *   chn       -
+ *   dir       -
+ *   span      -
+ *   data      -
+ *   conf_span -
+ *   ts        -
+ *   err       -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_scope_run_span(struct stream *s, struct filter *f, struct channel *chn, uint dir, struct flt_ot_scope_span *span, struct flt_ot_scope_data *data, const struct flt_ot_conf_span *conf_span, const struct timespec *ts, char **err)
+{
+	struct flt_ot_conf *conf = FLT_OT_CONF(f);
+	int                 retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p, %p, %u, %p, %p, %p, %p, %p:%p", s, f, chn, dir, span, data, conf_span, ts, FLT_OT_DPTR_ARGS(err));
+
+	if (span == NULL)
+		FLT_OT_RETURN(retval);
+
+	if (span->span == NULL) {
+		span->span = ot_span_init(conf->tracer->tracer, span->id, ts, NULL, span->ref_type, FLT_OT_DEREF(span->ref_ctx, idx, -1), span->ref_span, data->tags, data->num_tags, err);
+		if (span->span == NULL)
+			retval = FLT_OT_RET_ERROR;
+	}
+	else if (data->num_tags > 0)
+		if (ot_span_tag(span->span, data->tags, data->num_tags) == -1)
+			retval = FLT_OT_RET_ERROR;
+
+	if ((span->span != NULL) && (data->baggage != NULL))
+		if (ot_span_set_baggage(span->span, data->baggage) == -1)
+			retval = FLT_OT_RET_ERROR;
+
+	if ((span->span != NULL) && (data->num_log_fields > 0))
+		if (ot_span_log(span->span, data->log_fields, data->num_log_fields) == -1)
+			retval = FLT_OT_RET_ERROR;
+
+	if ((span->span != NULL) && (conf_span->ctx_id != NULL)) {
+		struct otc_http_headers_writer  writer;
+		struct otc_text_map            *text_map = NULL;
+		struct otc_span_context        *span_ctx;
+
+		span_ctx = ot_inject_http_headers(conf->tracer->tracer, span->span, &writer, err);
+		if (span_ctx != NULL) {
+			int i = 0;
+
+			if (conf_span->ctx_flags & (FLT_OT_CTX_USE_VARS | FLT_OT_CTX_USE_HEADERS)) {
+				for (text_map = &(writer.text_map); i < text_map->count; i++) {
+					if (!(conf_span->ctx_flags & FLT_OT_CTX_USE_VARS))
+						/* Do nothing. */;
+					else if (flt_ot_var_register(FLT_OT_VARS_SCOPE, conf_span->ctx_id, text_map->key[i], err) == -1)
+						retval = FLT_OT_RET_ERROR;
+					else if (flt_ot_var_set(s, FLT_OT_VARS_SCOPE, conf_span->ctx_id, text_map->key[i], text_map->value[i], dir, err) == -1)
+						retval = FLT_OT_RET_ERROR;
+
+					if (!(conf_span->ctx_flags & FLT_OT_CTX_USE_HEADERS))
+						/* Do nothing. */;
+					else if (flt_ot_http_header_set(chn, conf_span->ctx_id, text_map->key[i], text_map->value[i], err) == -1)
+						retval = FLT_OT_RET_ERROR;
+				}
+			}
+
+			span_ctx->destroy(&span_ctx);
+			otc_text_map_destroy(&text_map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+		}
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_run -
+ *
+ * ARGUMENTS
+ *   s          -
+ *   f          -
+ *   chn        -
+ *   conf_scope -
+ *   ts         -
+ *   dir        -
+ *   err        -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+int flt_ot_scope_run(struct stream *s, struct filter *f, struct channel *chn, struct flt_ot_conf_scope *conf_scope, const struct timespec *ts, uint dir, char **err)
+{
+	struct flt_ot_conf         *conf = FLT_OT_CONF(f);
+	struct flt_ot_conf_context *conf_ctx;
+	struct flt_ot_conf_span    *conf_span;
+	struct flt_ot_conf_str     *finish;
+	struct timespec             ts_now;
+	int                         retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p, %p, %p, %p, %u, %p:%p", s, f, chn, conf_scope, ts, dir, FLT_OT_DPTR_ARGS(err));
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+	FLT_OT_DBG(3, "run scope '%s' %d", conf_scope->id, conf_scope->event);
+	FLT_OT_DBG_CONF_SCOPE("run scope ", conf_scope);
+
+	if (ts == NULL) {
+		(void)clock_gettime(CLOCK_MONOTONIC, &ts_now);
+
+		ts = &ts_now;
+	}
+
+	if (conf_scope->cond != NULL) {
+		enum acl_test_res res;
+		int               rc;
+
+		res = acl_exec_cond(conf_scope->cond, s->be, s->sess, s, dir | SMP_OPT_FINAL);
+		rc  = acl_pass(res);
+		if (conf_scope->cond->pol == ACL_COND_UNLESS)
+			rc = !rc;
+
+		FLT_OT_DBG(3, "the ACL rule %s", rc ? "matches" : "does not match");
+
+		/*
+		 * If the rule does not match, the current scope is skipped.
+		 *
+		 * If it is a root span, further processing of the session is
+		 * disabled.  As soon as the first span is encountered which
+		 * is marked as root, further search is interrupted.
+		 */
+		if (!rc) {
+			list_for_each_entry(conf_span, &(conf_scope->spans), list)
+				if (conf_span->flag_root) {
+					FLT_OT_DBG(0, "session disabled");
+
+					FLT_OT_RT_CTX(f->ctx)->flag_disabled = 1;
+
+					_HA_ATOMIC_ADD(conf->cnt.disabled + 0, 1);
+
+					break;
+				}
+
+			FLT_OT_RETURN(retval);
+		}
+	}
+
+	list_for_each_entry(conf_ctx, &(conf_scope->contexts), list) {
+		struct otc_text_map *text_map;
+
+		FLT_OT_DBG(3, "run context '%s' -> '%s'", conf_scope->id, conf_ctx->id);
+		FLT_OT_DBG_CONF_CONTEXT("run context ", conf_ctx);
+
+		/*
+		 * The OpenTracing context is read from the HTTP header
+		 * or from HAProxy variables.
+		 */
+		if (conf_ctx->flags & FLT_OT_CTX_USE_HEADERS)
+			text_map = flt_ot_http_headers_get(chn, conf_ctx->id, conf_ctx->id_len, err);
+		else
+			text_map = flt_ot_vars_get(s, FLT_OT_VARS_SCOPE, conf_ctx->id, dir, err);
+
+		if (text_map != NULL) {
+			if (flt_ot_scope_context_init(f->ctx, conf->tracer->tracer, conf_ctx->id, conf_ctx->id_len, text_map, dir, err) == NULL)
+				retval = FLT_OT_RET_ERROR;
+
+			otc_text_map_destroy(&text_map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+		} else {
+			retval = FLT_OT_RET_ERROR;
+		}
+	}
+
+	list_for_each_entry(conf_span, &(conf_scope->spans), list) {
+		struct flt_ot_scope_data   data;
+		struct flt_ot_scope_span  *span;
+		struct flt_ot_conf_sample *sample;
+
+		FLT_OT_DBG(3, "run span '%s' -> '%s'", conf_scope->id, conf_span->id);
+		FLT_OT_DBG_CONF_SPAN("run span ", conf_span);
+
+		(void)memset(&data, 0, sizeof(data));
+
+		span = flt_ot_scope_span_init(f->ctx, conf_span->id, conf_span->id_len, conf_span->ref_type, conf_span->ref_id, conf_span->ref_id_len, dir, err);
+		if (span == NULL)
+			retval = FLT_OT_RET_ERROR;
+
+		list_for_each_entry(sample, &(conf_span->tags), list) {
+			FLT_OT_DBG(3, "adding tag '%s' -> '%s'", sample->key, sample->value);
+
+			if (flt_ot_sample_add(s, dir, sample, &data, FLT_OT_EVENT_SAMPLE_TAG, err) == FLT_OT_RET_ERROR)
+				retval = FLT_OT_RET_ERROR;
+		}
+
+		list_for_each_entry(sample, &(conf_span->logs), list) {
+			FLT_OT_DBG(3, "adding log '%s' -> '%s'", sample->key, sample->value);
+
+			if (flt_ot_sample_add(s, dir, sample, &data, FLT_OT_EVENT_SAMPLE_LOG, err) == FLT_OT_RET_ERROR)
+				retval = FLT_OT_RET_ERROR;
+		}
+
+		list_for_each_entry(sample, &(conf_span->baggages), list) {
+			FLT_OT_DBG(3, "adding baggage '%s' -> '%s'", sample->key, sample->value);
+
+			if (flt_ot_sample_add(s, dir, sample, &data, FLT_OT_EVENT_SAMPLE_BAGGAGE, err) == FLT_OT_RET_ERROR)
+				retval = FLT_OT_RET_ERROR;
+		}
+
+		if (retval != FLT_OT_RET_ERROR)
+			if (flt_ot_scope_run_span(s, f, chn, dir, span, &data, conf_span, ts, err) == FLT_OT_RET_ERROR)
+				retval = FLT_OT_RET_ERROR;
+
+		flt_ot_scope_data_free(&data);
+	}
+
+	list_for_each_entry(finish, &(conf_scope->finish), list)
+		if (flt_ot_scope_finish_mark(f->ctx, finish->str, finish->str_len) == -1)
+			retval = FLT_OT_RET_ERROR;
+
+	flt_ot_scope_finish_marked(f->ctx, ts);
+	flt_ot_scope_free_unused(f->ctx, chn);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_event_run -
+ *
+ * ARGUMENTS
+ *   s     -
+ *   f     -
+ *   chn   -
+ *   event -
+ *   err   -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+int flt_ot_event_run(struct stream *s, struct filter *f, struct channel *chn, int event, char **err)
+{
+	struct flt_ot_conf       *conf = FLT_OT_CONF(f);
+	struct flt_ot_conf_scope *conf_scope;
+	struct timespec           ts;
+	int                       retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p, %p, %d, %p:%p", s, f, chn, event, FLT_OT_DPTR_ARGS(err));
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+	FLT_OT_DBG(3, "run event '%s' %d", flt_ot_event_data[event].name, event);
+
+#ifdef DEBUG_OT
+	_HA_ATOMIC_ADD(conf->cnt.event[event].htx + (htx_is_empty(htxbuf(&(chn->buf))) ? 1 : 0), 1);
+#endif
+
+	FLT_OT_RT_CTX(f->ctx)->analyzers |= flt_ot_event_data[event].an_bit;
+
+	/* All spans should be created/completed at the same time. */
+	(void)clock_gettime(CLOCK_MONOTONIC, &ts);
+
+	/*
+	 * It is possible that there are defined multiple scopes that use the
+	 * same event.  Therefore, there must not be a 'break' here, ie an
+	 * exit from the 'for' loop.
+	 */
+	list_for_each_entry(conf_scope, &(conf->scopes), list) {
+		if (conf_scope->event != event)
+			/* Do nothing. */;
+		else if (!conf_scope->flag_used)
+			FLT_OT_DBG(3, "scope '%s' %d not used", conf_scope->id, conf_scope->event);
+		else if (flt_ot_scope_run(s, f, chn, conf_scope, &ts, flt_ot_event_data[event].smp_opt_dir, err) == FLT_OT_RET_ERROR)
+			retval = FLT_OT_RET_ERROR;
+	}
+
+	flt_ot_vars_dump(s);
+	flt_ot_http_headers_dump(chn);
+
+	FLT_OT_DBG(3, "event = %d, chn = %p, s->req = %p, s->res = %p", event, chn, &(s->req), &(s->res));
+
+	FLT_OT_RETURN(retval);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/filter.c b/addons/ot/src/filter.c
new file mode 100644
index 0000000..6699d46
--- /dev/null
+++ b/addons/ot/src/filter.c
@@ -0,0 +1,1153 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+/*
+ * OpenTracing filter id, used to identify OpenTracing filters.
+ * The name of this variable is consistent with the other filter names
+ * declared in include/haproxy/filters.h .
+ */
+const char *ot_flt_id = "the OpenTracing filter";
+
+
+/***
+ * NAME
+ *   flt_ot_is_disabled -
+ *
+ * ARGUMENTS
+ *   f     -
+ *   event -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+bool flt_ot_is_disabled(const struct filter *f FLT_OT_DBG_ARGS(, int event))
+{
+#ifdef DEBUG_OT
+	const struct flt_ot_conf *conf = FLT_OT_CONF(f);
+	const char               *msg;
+#endif
+	bool                      retval;
+
+	retval = FLT_OT_RT_CTX(f->ctx)->flag_disabled ? 1 : 0;
+
+#ifdef DEBUG_OT
+	msg    = retval ? " (disabled)" : "";
+
+	if (FLT_OT_IN_RANGE(event, 0, FLT_OT_EVENT_MAX - 1))
+		FLT_OT_DBG(2, "filter '%s', type: %s, event: '%s' %d%s", conf->id, flt_ot_type(f), flt_ot_event_data[event].name, event, msg);
+	else
+		FLT_OT_DBG(2, "filter '%s', type: %s%s", conf->id, flt_ot_type(f), msg);
+#endif
+
+	return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_return_int -
+ *
+ * ARGUMENTS
+ *   f      -
+ *   err    -
+ *   retval -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_return_int(const struct filter *f, char **err, int retval)
+{
+	struct flt_ot_runtime_context *rt_ctx = f->ctx;
+
+	if ((retval == FLT_OT_RET_ERROR) || ((err != NULL) && (*err != NULL))) {
+		if (rt_ctx->flag_harderr) {
+			FLT_OT_DBG(1, "WARNING: filter hard-error (disabled)");
+
+			rt_ctx->flag_disabled = 1;
+
+			_HA_ATOMIC_ADD(FLT_OT_CONF(f)->cnt.disabled + 1, 1);
+		} else {
+			FLT_OT_DBG(1, "WARNING: filter soft-error");
+		}
+
+		retval = FLT_OT_RET_OK;
+	}
+
+	FLT_OT_ERR_FREE(*err);
+
+	return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_return_void -
+ *
+ * ARGUMENTS
+ *   f   -
+ *   err -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_return_void(const struct filter *f, char **err)
+{
+	struct flt_ot_runtime_context *rt_ctx = f->ctx;
+
+	if ((err != NULL) && (*err != NULL)) {
+		if (rt_ctx->flag_harderr) {
+			FLT_OT_DBG(1, "WARNING: filter hard-error (disabled)");
+
+			rt_ctx->flag_disabled = 1;
+
+			_HA_ATOMIC_ADD(FLT_OT_CONF(f)->cnt.disabled + 1, 1);
+		} else {
+			FLT_OT_DBG(1, "WARNING: filter soft-error");
+		}
+	}
+
+	FLT_OT_ERR_FREE(*err);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_init - Initialize the filter.
+ *
+ * ARGUMENTS
+ *   p     -
+ *   fconf -
+ *
+ * DESCRIPTION
+ *   It initializes the filter for a proxy.  You may define this callback
+ *   if you need to complete your filter configuration.
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, any other value otherwise.
+ */
+static int flt_ot_init(struct proxy *p, struct flt_conf *fconf)
+{
+	struct flt_ot_conf *conf = FLT_OT_DEREF(fconf, conf, NULL);
+	char               *err = NULL;
+	int                 retval = FLT_OT_RET_ERROR;
+
+	FLT_OT_FUNC("%p, %p", p, fconf);
+
+	if (conf == NULL)
+		FLT_OT_RETURN(retval);
+
+	flt_ot_cli_init();
+
+	/*
+	 * Initialize the OpenTracing library.
+	 * Enable HTX streams filtering.
+	 */
+	retval = ot_init(&(conf->tracer->tracer), conf->tracer->config, conf->tracer->plugin, &err);
+	if (retval != FLT_OT_RET_ERROR)
+		fconf->flags |= FLT_CFG_FL_HTX;
+	else if (err != NULL) {
+		FLT_OT_ALERT("%s", err);
+
+		FLT_OT_ERR_FREE(err);
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_deinit - Free resources allocated by the filter.
+ *
+ * ARGUMENTS
+ *   p     -
+ *   fconf -
+ *
+ * DESCRIPTION
+ *   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.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_deinit(struct proxy *p, struct flt_conf *fconf)
+{
+	struct flt_ot_conf **conf = (fconf == NULL) ? NULL : (typeof(conf))&(fconf->conf);
+#ifdef DEBUG_OT
+	int                  i;
+#endif
+
+	FLT_OT_FUNC("%p, %p", p, fconf);
+
+	if (conf == NULL)
+		FLT_OT_RETURN();
+
+	ot_debug();
+	ot_close(&((*conf)->tracer->tracer));
+
+#ifdef DEBUG_OT
+	FLT_OT_DBG(0, "--- used events ----------");
+	for (i = 0; i < FLT_OT_TABLESIZE((*conf)->cnt.event); i++)
+		if ((*conf)->cnt.event[i].flag_used)
+			FLT_OT_DBG(0, "  %02d: %" PRIu64 " / %" PRIu64, i, (*conf)->cnt.event[i].htx[0], (*conf)->cnt.event[i].htx[1]);
+#endif
+
+	flt_ot_conf_free(conf);
+
+	FLT_OT_MEMINFO();
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_check - Check configuration of the filter for the specified proxy.
+ *
+ * ARGUMENTS
+ *   p     -
+ *   fconf -
+ *
+ * DESCRIPTION
+ *   Optionally, by implementing the flt_ot_check() callback, you add a
+ *   step to check the internal configuration of your filter after the
+ *   parsing phase, when the HAProxy configuration is fully defined.
+ *
+ * RETURN VALUE
+ *   Returns the number of encountered errors.
+ */
+static int flt_ot_check(struct proxy *p, struct flt_conf *fconf)
+{
+	struct proxy             *px;
+	struct flt_ot_conf       *conf = FLT_OT_DEREF(fconf, conf, NULL);
+	struct flt_ot_conf_group *conf_group;
+	struct flt_ot_conf_scope *conf_scope;
+	struct flt_ot_conf_ph    *ph_group, *ph_scope;
+	int                       retval = 0, scope_unused_cnt = 0, span_root_cnt = 0;
+
+	FLT_OT_FUNC("%p, %p", p, fconf);
+
+	if (conf == NULL)
+		FLT_OT_RETURN(++retval);
+
+	/*
+	 * If only the proxy specified with the <p> parameter is checked, then
+	 * no duplicate filters can be found that are not defined in the same
+	 * configuration sections.
+	 */
+	for (px = proxies_list; px != NULL; px = px->next) {
+		struct flt_conf *fconf_tmp;
+
+		FLT_OT_DBG(2, "proxy '%s'", px->id);
+
+		/*
+		 * The names of all OT filters (filter ID) should be checked,
+		 * they must be unique.
+		 */
+		list_for_each_entry(fconf_tmp, &(px->filter_configs), list)
+			if ((fconf_tmp != fconf) && (fconf_tmp->id == ot_flt_id)) {
+				struct flt_ot_conf *conf_tmp = fconf_tmp->conf;
+
+				FLT_OT_DBG(2, "  OT filter '%s'", conf_tmp->id);
+
+				if (strcmp(conf_tmp->id, conf->id) == 0) {
+					FLT_OT_ALERT("''%s' : duplicated filter ID'", conf_tmp->id);
+
+					retval++;
+				}
+			}
+	}
+
+	if (FLT_OT_DEREF(conf->tracer, id, NULL) == NULL) {
+		FLT_OT_ALERT("''%s' : no tracer found'", conf->id);
+
+		retval++;
+	}
+
+	/*
+	 * Checking that all defined 'ot-group' sections have correctly declared
+	 * 'ot-scope' sections (ie whether the declared 'ot-scope' sections have
+	 * corresponding definitions).
+	 */
+	list_for_each_entry(conf_group, &(conf->groups), list)
+		list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) {
+			bool flag_found = 0;
+
+			list_for_each_entry(conf_scope, &(conf->scopes), list)
+				if (strcmp(ph_scope->id, conf_scope->id) == 0) {
+					ph_scope->ptr         = conf_scope;
+					conf_scope->flag_used = 1;
+					flag_found            = 1;
+
+					break;
+				}
+
+			if (!flag_found) {
+				FLT_OT_ALERT("'" FLT_OT_PARSE_SECTION_GROUP_ID " '%s' : try to use undefined " FLT_OT_PARSE_SECTION_SCOPE_ID " '%s''", conf_group->id, ph_scope->id);
+
+				retval++;
+			}
+		}
+
+	if (conf->tracer != NULL) {
+		/*
+		 * Checking that all declared 'groups' keywords have correctly
+		 * defined 'ot-group' sections.
+		 */
+		list_for_each_entry(ph_group, &(conf->tracer->ph_groups), list) {
+			bool flag_found = 0;
+
+			list_for_each_entry(conf_group, &(conf->groups), list)
+				if (strcmp(ph_group->id, conf_group->id) == 0) {
+					ph_group->ptr         = conf_group;
+					conf_group->flag_used = 1;
+					flag_found            = 1;
+
+					break;
+				}
+
+			if (!flag_found) {
+				FLT_OT_ALERT("'" FLT_OT_PARSE_SECTION_TRACER_ID " '%s' : try to use undefined " FLT_OT_PARSE_SECTION_GROUP_ID " '%s''", conf->tracer->id, ph_group->id);
+
+				retval++;
+			}
+		}
+
+		/*
+		 * Checking that all declared 'scopes' keywords have correctly
+		 * defined 'ot-scope' sections.
+		 */
+		list_for_each_entry(ph_scope, &(conf->tracer->ph_scopes), list) {
+			bool flag_found = 0;
+
+			list_for_each_entry(conf_scope, &(conf->scopes), list)
+				if (strcmp(ph_scope->id, conf_scope->id) == 0) {
+					ph_scope->ptr         = conf_scope;
+					conf_scope->flag_used = 1;
+					flag_found            = 1;
+
+					break;
+				}
+
+			if (!flag_found) {
+				FLT_OT_ALERT("'" FLT_OT_PARSE_SECTION_TRACER_ID " '%s' : try to use undefined " FLT_OT_PARSE_SECTION_SCOPE_ID " '%s''", conf->tracer->id, ph_scope->id);
+
+				retval++;
+			}
+		}
+	}
+
+	FLT_OT_DBG(3, "--- filter '%s' configuration ----------", conf->id);
+	FLT_OT_DBG(3, "- defined spans ----------");
+
+	list_for_each_entry(conf_scope, &(conf->scopes), list) {
+		if (conf_scope->flag_used) {
+			struct flt_ot_conf_span *conf_span;
+
+			/*
+			 * In principle, only one span should be labeled
+			 * as a root span.
+			 */
+			list_for_each_entry(conf_span, &(conf_scope->spans), list) {
+				FLT_OT_DBG_CONF_SPAN("   ", conf_span);
+
+				span_root_cnt += conf_span->flag_root ? 1 : 0;
+			}
+
+#ifdef DEBUG_OT
+			conf->cnt.event[conf_scope->event].flag_used = 1;
+#endif
+
+			/* Set the flags of the analyzers used. */
+			conf->tracer->analyzers |= flt_ot_event_data[conf_scope->event].an_bit;
+		} else {
+			FLT_OT_ALERT("''%s' : unused " FLT_OT_PARSE_SECTION_SCOPE_ID " '%s''", conf->id, conf_scope->id);
+
+			scope_unused_cnt++;
+		}
+	}
+
+	/*
+	 * Unused scopes or a number of root spans other than one do not
+	 * necessarily have to be errors, but it is good to print it when
+	 * starting HAProxy.
+	 */
+	if (scope_unused_cnt > 0)
+		FLT_OT_ALERT("''%s' : %d scope(s) not in use'", conf->id, scope_unused_cnt);
+
+	if (LIST_ISEMPTY(&(conf->scopes)))
+		/* Do nothing. */;
+	else if (span_root_cnt == 0)
+		FLT_OT_ALERT("''%s' : no span is marked as the root span'", conf->id);
+	else if (span_root_cnt > 1)
+		FLT_OT_ALERT("''%s' : multiple spans are marked as the root span'", conf->id);
+
+	FLT_OT_DBG_LIST(conf, group, "", "defined", _group,
+	                FLT_OT_DBG_CONF_GROUP("   ", _group);
+	                FLT_OT_DBG_LIST(_group, ph_scope, "   ", "used", _scope, FLT_OT_DBG_CONF_PH("      ", _scope)));
+	FLT_OT_DBG_LIST(conf, scope, "", "defined", _scope, FLT_OT_DBG_CONF_SCOPE("   ", _scope));
+
+	if (conf->tracer != NULL) {
+		FLT_OT_DBG(3, "   --- tracer '%s' configuration ----------", conf->tracer->id);
+		FLT_OT_DBG_LIST(conf->tracer, ph_group, "   ", "used", _group, FLT_OT_DBG_CONF_PH("      ", _group));
+		FLT_OT_DBG_LIST(conf->tracer, ph_scope, "   ", "used", _scope, FLT_OT_DBG_CONF_PH("      ", _scope));
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_init_per_thread -
+ *
+ * ARGUMENTS
+ *   p     -
+ *   fconf -
+ *
+ * DESCRIPTION
+ *   It initializes the filter for each thread.  It works the same way than
+ *   flt_ot_init() but in the context of a thread.  This callback is called
+ *   after the thread creation.
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, any other value otherwise.
+ */
+static int flt_ot_init_per_thread(struct proxy *p, struct flt_conf *fconf)
+{
+	int retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p", p, fconf);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_deinit_per_thread -
+ *
+ * ARGUMENTS
+ *   p     -
+ *   fconf -
+ *
+ * DESCRIPTION
+ *   It cleans up what the init_per_thread callback have done.  It is called
+ *   in the context of a thread, before exiting it.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_deinit_per_thread(struct proxy *p, struct flt_conf *fconf)
+{
+	FLT_OT_FUNC("%p, %p", p, fconf);
+
+	FLT_OT_RETURN();
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_attach - Called when a filter instance is created and attach to a stream.
+ *
+ * ARGUMENTS
+ *   s -
+ *   f -
+ *
+ * DESCRIPTION
+ *   It is called after a 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.
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 to ignore the filter,
+ *   any other value otherwise.
+ */
+static int flt_ot_attach(struct stream *s, struct filter *f)
+{
+	const struct flt_ot_conf *conf = FLT_OT_CONF(f);
+	char                     *err = NULL;
+
+	FLT_OT_FUNC("%p, %p", s, f);
+
+	if (conf->tracer->flag_disabled) {
+		FLT_OT_DBG(2, "filter '%s', type: %s (disabled)", conf->id, flt_ot_type(f));
+
+		FLT_OT_RETURN(FLT_OT_RET_IGNORE);
+	}
+	else if (conf->tracer->rate_limit < FLT_OT_FLOAT_U32(FLT_OT_RATE_LIMIT_MAX, FLT_OT_RATE_LIMIT_MAX)) {
+		uint32_t rnd = ha_random32();
+
+		if (conf->tracer->rate_limit <= rnd) {
+			FLT_OT_DBG(2, "filter '%s', type: %s (ignored: %u <= %u)", conf->id, flt_ot_type(f), conf->tracer->rate_limit, rnd);
+
+			FLT_OT_RETURN(FLT_OT_RET_IGNORE);
+		}
+	}
+
+	FLT_OT_DBG(2, "filter '%s', type: %s (run)", conf->id, flt_ot_type(f));
+
+	f->ctx = flt_ot_runtime_context_init(s, f, &err);
+	FLT_OT_ERR_FREE(err);
+	if (f->ctx == NULL) {
+		FLT_OT_LOG(LOG_EMERG, "failed to create context");
+
+		FLT_OT_RETURN(FLT_OT_RET_IGNORE);
+	}
+
+	/*
+	 * AN_REQ_WAIT_HTTP and AN_RES_WAIT_HTTP analyzers can only be used
+	 * in the .channel_post_analyze callback function.
+	 */
+	f->pre_analyzers  |= conf->tracer->analyzers & ((AN_REQ_ALL & ~AN_REQ_WAIT_HTTP & ~AN_REQ_HTTP_TARPIT) | (AN_RES_ALL & ~AN_RES_WAIT_HTTP));
+	f->post_analyzers |= conf->tracer->analyzers & (AN_REQ_WAIT_HTTP | AN_RES_WAIT_HTTP);
+
+	FLT_OT_LOG(LOG_INFO, "%08x %08x", f->pre_analyzers, f->post_analyzers);
+
+	flt_ot_vars_dump(s);
+	flt_ot_http_headers_dump(&(s->req));
+
+	FLT_OT_RETURN(FLT_OT_RET_OK);
+}
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_stream_start - Called when a stream is created.
+ *
+ * ARGUMENTS
+ *   s -
+ *   f -
+ *
+ * DESCRIPTION
+ *   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.
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, any other value otherwise.
+ */
+static int flt_ot_stream_start(struct stream *s, struct filter *f)
+{
+	char *err = NULL;
+	int   retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p", s, f);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN(retval);
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_stream_set_backend - Called when a backend is set for a stream.
+ *
+ * ARGUMENTS
+ *   s  -
+ *   f  -
+ *   be -
+ *
+ * DESCRIPTION
+ *   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.
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, any other value otherwise.
+ */
+static int flt_ot_stream_set_backend(struct stream *s, struct filter *f, struct proxy *be)
+{
+	char *err = NULL;
+	int   retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p, %p", s, f, be);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN(retval);
+
+	FLT_OT_DBG(3, "backend: %s", be->id);
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_stream_stop - Called when a stream is destroyed.
+ *
+ * ARGUMENTS
+ *   s -
+ *   f -
+ *
+ * DESCRIPTION
+ *   It is called when a stream is stopped.  This callback always succeed.
+ *   Anyway, it is too late to return an error.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_stream_stop(struct stream *s, struct filter *f)
+{
+	char *err = NULL;
+
+	FLT_OT_FUNC("%p, %p", s, f);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN();
+
+	flt_ot_return_void(f, &err);
+
+	FLT_OT_RETURN();
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_detach - Called when a filter instance is detach from a stream, just before its destruction.
+ *
+ * ARGUMENTS
+ *   s -
+ *   f -
+ *
+ * DESCRIPTION
+ *   It is called when a 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.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_detach(struct stream *s, struct filter *f)
+{
+	FLT_OT_FUNC("%p, %p", s, f);
+
+	FLT_OT_DBG(2, "filter '%s', type: %s", FLT_OT_CONF(f)->id, flt_ot_type(f));
+
+	flt_ot_runtime_context_free(f);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_check_timeouts - Called when a stream is woken up because of an expired timer.
+ *
+ * ARGUMENTS
+ *   s -
+ *   f -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_check_timeouts(struct stream *s, struct filter *f)
+{
+	char *err = NULL;
+
+	FLT_OT_FUNC("%p, %p", s, f);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN();
+
+	s->pending_events |= TASK_WOKEN_MSG;
+
+	flt_ot_return_void(f, &err);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_channel_start_analyze - Called when analyze starts for a given channel.
+ *
+ * ARGUMENTS
+ *   s   -
+ *   f   -
+ *   chn -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_channel_start_analyze(struct stream *s, struct filter *f, struct channel *chn)
+{
+	char *err = NULL;
+	int   retval;
+
+	FLT_OT_FUNC("%p, %p, %p", s, f, chn);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, (chn->flags & CF_ISRESP) ? FLT_OT_EVENT_RES_SERVER_SESS_START : FLT_OT_EVENT_REQ_CLIENT_SESS_START)))
+		FLT_OT_RETURN(FLT_OT_RET_OK);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+
+	if (chn->flags & CF_ISRESP) {
+		/* The response channel. */
+		chn->analysers |= f->pre_analyzers & AN_RES_ALL;
+
+		/* The event 'on-server-session-start'. */
+		retval = flt_ot_event_run(s, f, chn, FLT_OT_EVENT_RES_SERVER_SESS_START, &err);
+		if (retval == FLT_OT_RET_WAIT) {
+			channel_dont_read(chn);
+			channel_dont_close(chn);
+		}
+	} else {
+		/* The request channel. */
+		chn->analysers |= f->pre_analyzers & AN_REQ_ALL;
+
+		/* The event 'on-client-session-start'. */
+		retval = flt_ot_event_run(s, f, chn, FLT_OT_EVENT_REQ_CLIENT_SESS_START, &err);
+	}
+
+//	register_data_filter(s, chn, f);
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_channel_pre_analyze - Called before a processing happens on a given channel.
+ *
+ * ARGUMENTS
+ *   s      -
+ *   f      -
+ *   chn    - the channel on which the analyzing is done
+ *   an_bit - the analyzer id
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_channel_pre_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit)
+{
+	char *err = NULL;
+	int   i, event = -1, retval;
+
+	FLT_OT_FUNC("%p, %p, %p, 0x%08x", s, f, chn, an_bit);
+
+	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_event_data); i++)
+		if (flt_ot_event_data[i].an_bit == an_bit) {
+			event = i;
+
+			break;
+		}
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, event)))
+		FLT_OT_RETURN(FLT_OT_RET_OK);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s), analyzer: %s", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s), flt_ot_analyzer(an_bit));
+
+	retval = flt_ot_event_run(s, f, chn, event, &err);
+
+	if ((retval == FLT_OT_RET_WAIT) && (chn->flags & CF_ISRESP)) {
+		channel_dont_read(chn);
+		channel_dont_close(chn);
+	}
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_channel_post_analyze - Called after a processing happens on a given channel.
+ *
+ * ARGUMENTS
+ *   s      -
+ *   f      -
+ *   chn    -
+ *   an_bit -
+ *
+ * DESCRIPTION
+ *   This function, for its part, is not resumable.  It is called when a
+ *   filterable analyzer finishes its processing.  So it called once for
+ *   the same analyzer.
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_channel_post_analyze(struct stream *s, struct filter *f, struct channel *chn, uint an_bit)
+{
+	char *err = NULL;
+	int   i, event = -1, retval;
+
+	FLT_OT_FUNC("%p, %p, %p, 0x%08x", s, f, chn, an_bit);
+
+	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_event_data); i++)
+		if (flt_ot_event_data[i].an_bit == an_bit) {
+			event = i;
+
+			break;
+		}
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, event)))
+		FLT_OT_RETURN(FLT_OT_RET_OK);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s), analyzer: %s", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s), flt_ot_analyzer(an_bit));
+
+	retval = flt_ot_event_run(s, f, chn, event, &err);
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_channel_end_analyze - Called when analyze ends for a given channel.
+ *
+ * ARGUMENTS
+ *   s   -
+ *   f   -
+ *   chn -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_channel_end_analyze(struct stream *s, struct filter *f, struct channel *chn)
+{
+	char *err = NULL;
+	int   rc, retval;
+
+	FLT_OT_FUNC("%p, %p, %p", s, f, chn);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, (chn->flags & CF_ISRESP) ? FLT_OT_EVENT_RES_SERVER_SESS_END : FLT_OT_EVENT_REQ_CLIENT_SESS_END)))
+		FLT_OT_RETURN(FLT_OT_RET_OK);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+
+	if (chn->flags & CF_ISRESP) {
+		/* The response channel, event 'on-server-session-end'. */
+		retval = flt_ot_event_run(s, f, chn, FLT_OT_EVENT_RES_SERVER_SESS_END, &err);
+	} else {
+		/* The request channel, event 'on-client-session-end'. */
+		retval = flt_ot_event_run(s, f, chn, FLT_OT_EVENT_REQ_CLIENT_SESS_END, &err);
+
+		/*
+		 * In case an event using server response is defined and not
+		 * executed, event 'on-server-unavailable' is called here.
+		 */
+		if ((FLT_OT_CONF(f)->tracer->analyzers & AN_RES_ALL) && !(FLT_OT_RT_CTX(f->ctx)->analyzers & AN_RES_ALL)) {
+			rc = flt_ot_event_run(s, f, chn, FLT_OT_EVENT_REQ_SERVER_UNAVAILABLE, &err);
+			if ((retval == FLT_OT_RET_OK) && (rc != FLT_OT_RET_OK))
+				retval = rc;
+		}
+	}
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_http_headers -
+ *
+ * ARGUMENTS
+ *   s   -
+ *   f   -
+ *   msg -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_http_headers(struct stream *s, struct filter *f, struct http_msg *msg)
+{
+	char          *err = NULL;
+	struct htx    *htx = htxbuf(&(msg->chn->buf));
+	struct htx_sl *sl = http_get_stline(htx);
+	int            retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p, %p", s, f, msg);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN(retval);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s), %.*s %.*s %.*s", flt_ot_chn_label(msg->chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s), HTX_SL_P1_LEN(sl), HTX_SL_P1_PTR(sl), HTX_SL_P2_LEN(sl), HTX_SL_P2_PTR(sl), HTX_SL_P3_LEN(sl), HTX_SL_P3_PTR(sl));
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_http_payload -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   f      -
+ *   msg    -
+ *   offset -
+ *   len    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, any other value otherwise.
+ */
+static int flt_ot_http_payload(struct stream *s, struct filter *f, struct http_msg *msg, uint offset, uint len)
+{
+	char *err = NULL;
+	int   retval = len;
+
+	FLT_OT_FUNC("%p, %p, %p, %u, %u", s, f, msg, offset, len);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN(len);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s), offset: %u, len: %u, forward: %d", flt_ot_chn_label(msg->chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s), offset, len, retval);
+
+	if (retval != len)
+		task_wakeup(s->task, TASK_WOKEN_MSG);
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_http_end -
+ *
+ * ARGUMENTS
+ *   s   -
+ *   f   -
+ *   msg -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+static int flt_ot_http_end(struct stream *s, struct filter *f, struct http_msg *msg)
+{
+	char *err = NULL;
+	int   retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %p, %p", s, f, msg);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN(retval);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(msg->chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_http_reset -
+ *
+ * ARGUMENTS
+ *   s   -
+ *   f   -
+ *   msg -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_http_reset(struct stream *s, struct filter *f, struct http_msg *msg)
+{
+	char *err = NULL;
+
+	FLT_OT_FUNC("%p, %p, %p", s, f, msg);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s)", flt_ot_chn_label(msg->chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+
+	flt_ot_return_void(f, &err);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_http_reply -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   f      -
+ *   status -
+ *   msg    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_http_reply(struct stream *s, struct filter *f, short status, const struct buffer *msg)
+{
+	char *err = NULL;
+
+	FLT_OT_FUNC("%p, %p, %hd, %p", s, f, status, msg);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG(3, "channel: -, mode: %s (%s)", flt_ot_pr_mode(s), flt_ot_stream_pos(s));
+
+	flt_ot_return_void(f, &err);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_tcp_payload -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   f      -
+ *   chn    -
+ *   offset -
+ *   len    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, any other value otherwise.
+ */
+static int flt_ot_tcp_payload(struct stream *s, struct filter *f, struct channel *chn, uint offset, uint len)
+{
+	char *err = NULL;
+	int   retval = len;
+
+	FLT_OT_FUNC("%p, %p, %p, %u, %u", s, f, chn, offset, len);
+
+	if (flt_ot_is_disabled(f FLT_OT_DBG_ARGS(, -1)))
+		FLT_OT_RETURN(len);
+
+	FLT_OT_DBG(3, "channel: %s, mode: %s (%s), offset: %u, len: %u, forward: %d", flt_ot_chn_label(chn), flt_ot_pr_mode(s), flt_ot_stream_pos(s), offset, len, retval);
+
+	if (s->flags & SF_HTX) {
+	} else {
+	}
+
+	if (retval != len)
+		task_wakeup(s->task, TASK_WOKEN_MSG);
+
+	FLT_OT_RETURN(flt_ot_return_int(f, &err, retval));
+}
+
+#endif /* DEBUG_OT */
+
+
+struct flt_ops flt_ot_ops = {
+	/* Callbacks to manage the filter lifecycle. */
+	.init                  = flt_ot_init,
+	.deinit                = flt_ot_deinit,
+	.check                 = flt_ot_check,
+	.init_per_thread       = FLT_OT_DBG_IFDEF(flt_ot_init_per_thread, NULL),
+	.deinit_per_thread     = FLT_OT_DBG_IFDEF(flt_ot_deinit_per_thread, NULL),
+
+	/* Stream callbacks. */
+	.attach                = flt_ot_attach,
+	.stream_start          = FLT_OT_DBG_IFDEF(flt_ot_stream_start, NULL),
+	.stream_set_backend    = FLT_OT_DBG_IFDEF(flt_ot_stream_set_backend, NULL),
+	.stream_stop           = FLT_OT_DBG_IFDEF(flt_ot_stream_stop, NULL),
+	.detach                = flt_ot_detach,
+	.check_timeouts        = flt_ot_check_timeouts,
+
+	/* Channel callbacks. */
+	.channel_start_analyze = flt_ot_channel_start_analyze,
+	.channel_pre_analyze   = flt_ot_channel_pre_analyze,
+	.channel_post_analyze  = flt_ot_channel_post_analyze,
+	.channel_end_analyze   = flt_ot_channel_end_analyze,
+
+	/* HTTP callbacks. */
+	.http_headers          = FLT_OT_DBG_IFDEF(flt_ot_http_headers, NULL),
+	.http_payload          = FLT_OT_DBG_IFDEF(flt_ot_http_payload, NULL),
+	.http_end              = FLT_OT_DBG_IFDEF(flt_ot_http_end, NULL),
+	.http_reset            = FLT_OT_DBG_IFDEF(flt_ot_http_reset, NULL),
+	.http_reply            = FLT_OT_DBG_IFDEF(flt_ot_http_reply, NULL),
+
+	/* TCP callbacks. */
+	.tcp_payload           = FLT_OT_DBG_IFDEF(flt_ot_tcp_payload, NULL)
+};
+
+
+REGISTER_BUILD_OPTS("Built with OpenTracing support.");
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/group.c b/addons/ot/src/group.c
new file mode 100644
index 0000000..f9fdecc
--- /dev/null
+++ b/addons/ot/src/group.c
@@ -0,0 +1,354 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+#define FLT_OT_GROUP_DEF(a,b,c)   { a, b, c },
+const struct flt_ot_group_data flt_ot_group_data[] = { FLT_OT_GROUP_DEFINES };
+#undef FLT_OT_GROUP_DEF
+
+
+/***
+ * NAME
+ *   flt_ot_group_action -
+ *
+ * ARGUMENTS
+ *   rule -
+ *   px   -
+ *   sess -
+ *   s    -
+ *   opts -
+ *
+ * DESCRIPTION
+ *   This is the action_ptr callback of a rule associated to the
+ *   FLT_OT_ACTION_GROUP action.
+ *
+ * RETURN VALUE
+ *   The function returns ACT_RET_CONT if processing is finished (with error or
+ *   not), otherwise, it returns ACT_RET_YIELD if the action is in progress.
+ */
+static enum act_return flt_ot_group_action(struct act_rule *rule, struct proxy *px, struct session *sess, struct stream *s, int opts)
+{
+	const struct filter                 *filter;
+	const struct flt_conf               *fconf;
+	const struct flt_ot_conf            *conf;
+	const struct flt_ot_conf_group      *conf_group;
+	const struct flt_ot_runtime_context *rt_ctx = NULL;
+	const struct flt_ot_conf_ph         *ph_scope;
+	char                                *err = NULL;
+	int                                  i, rc;
+
+	FLT_OT_FUNC("%p, %p, %p, %p, %d", rule, px, sess, s, opts);
+
+	FLT_OT_DBG(3, "from: %d, arg.act %p:{ %p %p %p %p }", rule->from, &(rule->arg.act), rule->arg.act.p[0], rule->arg.act.p[1], rule->arg.act.p[2], rule->arg.act.p[3]);
+
+	fconf      = rule->arg.act.p[FLT_OT_ARG_FLT_CONF];
+	conf       = rule->arg.act.p[FLT_OT_ARG_CONF];
+	conf_group = ((const struct flt_ot_conf_ph *)(rule->arg.act.p[FLT_OT_ARG_GROUP]))->ptr;
+
+	if ((fconf == NULL) || (conf == NULL) || (conf_group == NULL)) {
+		FLT_OT_LOG(LOG_ERR, FLT_OT_ACTION_GROUP ": internal error, invalid group action");
+
+		FLT_OT_RETURN(ACT_RET_CONT);
+	}
+
+	if (conf->tracer->flag_disabled) {
+		FLT_OT_DBG(1, "filter '%s' disabled, group action '%s' ignored", conf->id, conf_group->id);
+
+		FLT_OT_RETURN(ACT_RET_CONT);
+	}
+
+	/* Find the OpenTracing filter instance from the current stream. */
+	list_for_each_entry(filter, &(s->strm_flt.filters), list)
+		if (filter->config == fconf) {
+			rt_ctx = filter->ctx;
+
+			break;
+		}
+
+	if (rt_ctx == NULL) {
+		FLT_OT_DBG(1, "cannot find filter, probably not attached to the stream");
+
+		FLT_OT_RETURN(ACT_RET_CONT);
+	}
+	else if (flt_ot_is_disabled(filter FLT_OT_DBG_ARGS(, -1))) {
+		FLT_OT_RETURN(ACT_RET_CONT);
+	}
+	else {
+		FLT_OT_DBG(3, "run group '%s'", conf_group->id);
+		FLT_OT_DBG_CONF_GROUP("run group ", conf_group);
+	}
+
+	/*
+	 * Check the value of rule->from; in case it is incorrect,
+	 * report an error.
+	 */
+	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_group_data); i++)
+		if (flt_ot_group_data[i].act_from == rule->from)
+			break;
+
+	if (i >= FLT_OT_TABLESIZE(flt_ot_group_data)) {
+		FLT_OT_LOG(LOG_ERR, FLT_OT_ACTION_GROUP ": internal error, invalid rule->from=%d", rule->from);
+
+		FLT_OT_RETURN(ACT_RET_CONT);
+	}
+
+	list_for_each_entry(ph_scope, &(conf_group->ph_scopes), list) {
+		rc = flt_ot_scope_run(s, rt_ctx->filter, &(s->res), ph_scope->ptr, NULL, SMP_OPT_DIR_RES, &err);
+		if ((rc == FLT_OT_RET_ERROR) && (opts & ACT_OPT_FINAL)) {
+			/* XXX */
+		}
+	}
+
+	FLT_OT_RETURN(ACT_RET_CONT);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_group_check -
+ *
+ * ARGUMENTS
+ *   rule -
+ *   px   -
+ *   err  -
+ *
+ * DESCRIPTION
+ *   This is the check_ptr callback of a rule associated to the
+ *   FLT_OT_ACTION_GROUP action.
+ *
+ * RETURN VALUE
+ *   The function returns 1 in success case, otherwise,
+ *   it returns 0 and err is filled.
+ */
+static int flt_ot_group_check(struct act_rule *rule, struct proxy *px, char **err)
+{
+	struct flt_conf       *fconf_tmp, *fconf = NULL;
+	struct flt_ot_conf    *conf;
+	struct flt_ot_conf_ph *ph_group;
+	const char            *filter_id;
+	const char            *group_id;
+	bool                   flag_found = 0;
+	int                    i;
+
+	FLT_OT_FUNC("%p, %p, %p:%p", rule, px, FLT_OT_DPTR_ARGS(err));
+
+	filter_id = rule->arg.act.p[FLT_OT_ARG_FILTER_ID];
+	group_id  = rule->arg.act.p[FLT_OT_ARG_GROUP_ID];
+
+	FLT_OT_DBG(2, "checking filter_id='%s', group_id='%s'", filter_id, group_id);
+
+	/*
+	 * Check the value of rule->from; in case it is incorrect,
+	 * report an error.
+	 */
+	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_group_data); i++)
+		if (flt_ot_group_data[i].act_from == rule->from)
+			break;
+
+	if (i >= FLT_OT_TABLESIZE(flt_ot_group_data)) {
+		FLT_OT_ERR("internal error, unexpected rule->from=%d, please report this bug!", rule->from);
+
+		FLT_OT_RETURN(0);
+	}
+
+	/*
+	 * Try to find the OpenTracing filter by checking all filters
+	 * for the proxy <px>.
+	 */
+	list_for_each_entry(fconf_tmp, &(px->filter_configs), list) {
+		conf = fconf_tmp->conf;
+
+		if (fconf_tmp->id != ot_flt_id) {
+			/* This is not an OpenTracing filter. */
+			continue;
+		}
+		else if (strcmp(conf->id, filter_id) == 0) {
+			/* This is the good filter ID. */
+			fconf = fconf_tmp;
+
+			break;
+		}
+	}
+
+	if (fconf == NULL) {
+		FLT_OT_ERR("unable to find the OpenTracing filter '%s' used by the " FLT_OT_ACTION_GROUP " '%s'", filter_id, group_id);
+
+		FLT_OT_RETURN(0);
+	}
+
+	/*
+	 * Attempt to find if the group is defined in the OpenTracing filter
+	 * configuration.
+	 */
+	list_for_each_entry(ph_group, &(conf->tracer->ph_groups), list)
+		if (strcmp(ph_group->id, group_id) == 0) {
+			flag_found = 1;
+
+			break;
+		}
+
+	if (!flag_found) {
+		FLT_OT_ERR("unable to find group '%s' in the OpenTracing filter '%s' configuration", group_id, filter_id);
+
+		FLT_OT_RETURN(0);
+	}
+
+	FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_FILTER_ID]);
+	FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_GROUP_ID]);
+
+	rule->arg.act.p[FLT_OT_ARG_FLT_CONF] = fconf;
+	rule->arg.act.p[FLT_OT_ARG_CONF]     = conf;
+	rule->arg.act.p[FLT_OT_ARG_GROUP]    = ph_group;
+
+	FLT_OT_RETURN(1);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_group_release -
+ *
+ * ARGUMENTS
+ *   rule -
+ *
+ * DESCRIPTION
+ *   This is the release_ptr callback of a rule associated to the
+ *   FLT_OT_ACTION_GROUP action.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_group_release(struct act_rule *rule)
+{
+	FLT_OT_FUNC("%p", rule);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_group_parse -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   cur_arg -
+ *   px      -
+ *   rule    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ACT_RET_PRS_ERR if an error occurs, ACT_RET_PRS_OK otherwise.
+ */
+static enum act_parse_ret flt_ot_group_parse(const char **args, int *cur_arg, struct proxy *px, struct act_rule *rule, char **err)
+{
+	FLT_OT_FUNC("%p, %p, %p, %p, %p:%p", args, cur_arg, px, rule, FLT_OT_DPTR_ARGS(err));
+
+	if (!FLT_OT_ARG_ISVALID(*cur_arg) ||
+	    !FLT_OT_ARG_ISVALID(*cur_arg + 1) ||
+	    (FLT_OT_ARG_ISVALID(*cur_arg + 2) &&
+	     (strcmp(args[*cur_arg + 2], FLT_OT_CONDITION_IF) != 0) &&
+	     (strcmp(args[*cur_arg + 2], FLT_OT_CONDITION_UNLESS) != 0))) {
+		FLT_OT_ERR("expects: <filter-id> <group-id> [{ if | unless } ...]");
+
+		FLT_OT_RETURN(ACT_RET_PRS_ERR);
+	}
+
+	/* Copy the OpenTracing filter id. */
+	rule->arg.act.p[FLT_OT_ARG_FILTER_ID] = FLT_OT_STRDUP(args[*cur_arg]);
+	if (rule->arg.act.p[FLT_OT_ARG_FILTER_ID] == NULL) {
+		FLT_OT_ERR("%s : out of memory", args[*cur_arg]);
+
+		FLT_OT_RETURN(ACT_RET_PRS_ERR);
+	}
+
+	/* Copy the OpenTracing group id. */
+	rule->arg.act.p[FLT_OT_ARG_GROUP_ID] = FLT_OT_STRDUP(args[*cur_arg + 1]);
+	if (rule->arg.act.p[FLT_OT_ARG_GROUP_ID] == NULL) {
+		FLT_OT_ERR("%s : out of memory", args[*cur_arg + 1]);
+
+		FLT_OT_FREE_CLEAR(rule->arg.act.p[FLT_OT_ARG_FILTER_ID]);
+
+		FLT_OT_RETURN(ACT_RET_PRS_ERR);
+	}
+
+	rule->action      = ACT_CUSTOM;
+	rule->action_ptr  = flt_ot_group_action;
+	rule->check_ptr   = flt_ot_group_check;
+	rule->release_ptr = flt_ot_group_release;
+
+	*cur_arg += 2;
+
+	FLT_OT_RETURN(ACT_RET_PRS_OK);
+}
+
+
+static struct action_kw_list tcp_req_action_kws = { ILH, {
+		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
+		{ /* END */ },
+	}
+};
+
+INITCALL1(STG_REGISTER, tcp_req_cont_keywords_register, &tcp_req_action_kws);
+
+static struct action_kw_list tcp_res_action_kws = { ILH, {
+		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
+		{ /* END */ },
+	}
+};
+
+INITCALL1(STG_REGISTER, tcp_res_cont_keywords_register, &tcp_res_action_kws);
+
+static struct action_kw_list http_req_action_kws = { ILH, {
+		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
+		{ /* END */ },
+	}
+};
+
+INITCALL1(STG_REGISTER, http_req_keywords_register, &http_req_action_kws);
+
+static struct action_kw_list http_res_action_kws = { ILH, {
+		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
+		{ /* END */ },
+	}
+};
+
+INITCALL1(STG_REGISTER, http_res_keywords_register, &http_res_action_kws);
+
+static struct action_kw_list http_after_res_actions_kws = { ILH, {
+		{ FLT_OT_ACTION_GROUP, flt_ot_group_parse },
+		{ /* END */ },
+	}
+};
+
+INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_actions_kws);
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/http.c b/addons/ot/src/http.c
new file mode 100644
index 0000000..72b31b7
--- /dev/null
+++ b/addons/ot/src/http.c
@@ -0,0 +1,291 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_http_headers_dump -
+ *
+ * ARGUMENTS
+ *   chn -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_http_headers_dump(const struct channel *chn)
+{
+	const struct htx *htx;
+	int32_t           pos;
+
+	FLT_OT_FUNC("%p", chn);
+
+	if (chn == NULL)
+		FLT_OT_RETURN();
+
+	htx = htxbuf(&(chn->buf));
+
+	if (htx_is_empty(htx))
+		FLT_OT_RETURN();
+
+	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);
+
+		if (type == HTX_BLK_HDR) {
+			struct ist n = htx_get_blk_name(htx, blk);
+			struct ist v = htx_get_blk_value(htx, blk);
+
+			FLT_OT_DBG(2, "'%.*s: %.*s'", (int)n.len, n.ptr, (int)v.len, v.ptr);
+		}
+		else if (type == HTX_BLK_EOH)
+			break;
+	}
+
+	FLT_OT_RETURN();
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_http_headers_get -
+ *
+ * ARGUMENTS
+ *   chn    -
+ *   prefix -
+ *   len    -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   This function is very similar to function http_action_set_header(), from
+ *   the HAProxy source.
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_text_map *flt_ot_http_headers_get(struct channel *chn, const char *prefix, size_t len, char **err)
+{
+	const struct htx    *htx;
+	size_t               prefix_len = (!FLT_OT_STR_ISVALID(prefix) || (len == 0)) ? 0 : (len + 1);
+	int32_t              pos;
+	struct otc_text_map *retptr = NULL;
+
+	FLT_OT_FUNC("%p, \"%s\", %zu, %p:%p", chn, prefix, len, FLT_OT_DPTR_ARGS(err));
+
+	if (chn == NULL)
+		FLT_OT_RETURN(retptr);
+
+	htx = htxbuf(&(chn->buf));
+
+	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);
+
+		if (type == HTX_BLK_HDR) {
+			struct ist v, n = htx_get_blk_name(htx, blk);
+
+			if ((prefix_len == 0) || ((n.len >= prefix_len) && (strncasecmp(n.ptr, prefix, len) == 0))) {
+				if (retptr == NULL) {
+					retptr = otc_text_map_new(NULL, 8);
+					if (retptr == NULL) {
+						FLT_OT_ERR("failed to create HTTP header data");
+
+						break;
+					}
+				}
+
+				v = htx_get_blk_value(htx, blk);
+
+				/*
+				 * Here, an HTTP header (which is actually part
+				 * of the span context is added to the text_map.
+				 *
+				 * Before adding, the prefix is removed from the
+				 * HTTP header name.
+				 */
+				if (otc_text_map_add(retptr, n.ptr + prefix_len, n.len - prefix_len, v.ptr, v.len, OTC_TEXT_MAP_DUP_KEY | OTC_TEXT_MAP_DUP_VALUE) == -1) {
+					FLT_OT_ERR("failed to add HTTP header data");
+
+					otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+
+					break;
+				}
+			}
+		}
+		else if (type == HTX_BLK_EOH)
+			break;
+	}
+
+	ot_text_map_show(retptr);
+
+	if ((retptr != NULL) && (retptr->count == 0)) {
+		FLT_OT_DBG(2, "WARNING: no HTTP headers found");
+
+		otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_http_header_set -
+ *
+ * ARGUMENTS
+ *   chn    -
+ *   prefix -
+ *   name   -
+ *   value  -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   This function is very similar to function http_action_set_header(), from
+ *   the HAProxy source.
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_http_header_set(struct channel *chn, const char *prefix, const char *name, const char *value, char **err)
+{
+	struct http_hdr_ctx  ctx = { .blk = NULL };
+	struct ist           ist_name;
+	struct buffer       *buffer = NULL;
+	struct htx          *htx;
+	int                  retval = -1;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", %p:%p", chn, prefix, name, value, FLT_OT_DPTR_ARGS(err));
+
+	if ((chn == NULL) || (!FLT_OT_STR_ISVALID(prefix) && !FLT_OT_STR_ISVALID(name)))
+		FLT_OT_RETURN(retval);
+
+	htx = htxbuf(&(chn->buf));
+
+	/*
+	 * Very rare (about 1% of cases), htx is empty.
+	 * In order to avoid segmentation fault, we exit this function.
+	 */
+	if (htx_is_empty(htx)) {
+		FLT_OT_ERR("HTX is empty");
+
+		FLT_OT_RETURN(retval);
+	}
+
+	if (!FLT_OT_STR_ISVALID(prefix)) {
+		ist_name.ptr = (char *)name;
+		ist_name.len = strlen(name);
+	}
+	else if (!FLT_OT_STR_ISVALID(name)) {
+		ist_name.ptr = (char *)prefix;
+		ist_name.len = strlen(prefix);
+	}
+	else {
+		buffer = flt_ot_trash_alloc(0, err);
+		if (buffer == NULL)
+			FLT_OT_RETURN(retval);
+
+		(void)chunk_printf(buffer, "%s-%s", prefix, name);
+
+		ist_name.ptr = buffer->area;
+		ist_name.len = buffer->data;
+	}
+
+	/* Remove all occurrences of the header. */
+	while (http_find_header(htx, ist(""), &ctx, 1) == 1) {
+		struct ist n = htx_get_blk_name(htx, ctx.blk);
+#ifdef DEBUG_OT
+		struct ist v = htx_get_blk_value(htx, ctx.blk);
+#endif
+
+		/*
+		 * If the <name> parameter is not set, then remove all headers
+		 * that start with the contents of the <prefix> parameter.
+		 */
+		if (!FLT_OT_STR_ISVALID(name))
+			n.len = ist_name.len;
+
+		if (isteqi(n, ist_name))
+			if (http_remove_header(htx, &ctx) == 1)
+				FLT_OT_DBG(3, "HTTP header '%.*s: %.*s' removed", (int)n.len, n.ptr, (int)v.len, v.ptr);
+	}
+
+	/*
+	 * If the value pointer has a value of NULL, the HTTP header is not set
+	 * after deletion.
+	 */
+	if (value == NULL) {
+		/* Do nothing. */
+	}
+	else if (http_add_header(htx, ist_name, ist(value)) == 1) {
+		retval = 0;
+
+		FLT_OT_DBG(3, "HTTP header '%s: %s' added", ist_name.ptr, value);
+	}
+	else {
+		FLT_OT_ERR("failed to set HTTP header '%s: %s'", ist_name.ptr, value);
+	}
+
+	flt_ot_trash_free(&buffer);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_http_headers_remove -
+ *
+ * ARGUMENTS
+ *   chn    -
+ *   prefix -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_http_headers_remove(struct channel *chn, const char *prefix, char **err)
+{
+	int retval;
+
+	FLT_OT_FUNC("%p, \"%s\", %p:%p", chn, prefix, FLT_OT_DPTR_ARGS(err));
+
+	retval = flt_ot_http_header_set(chn, prefix, NULL, NULL, err);
+
+	FLT_OT_RETURN(retval);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/opentracing.c b/addons/ot/src/opentracing.c
new file mode 100644
index 0000000..b79b90a
--- /dev/null
+++ b/addons/ot/src/opentracing.c
@@ -0,0 +1,1035 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+static struct pool_head *pool_head_ot_span_context = NULL;
+
+#ifdef USE_POOL_OT_SPAN_CONTEXT
+REGISTER_POOL(&pool_head_ot_span_context, "ot_span_context", MAX(sizeof(struct otc_span), sizeof(struct otc_span_context)));
+#endif
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   ot_text_map_show -
+ *
+ * ARGUMENTS
+ *   text_map -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void ot_text_map_show(const struct otc_text_map *text_map)
+{
+	FLT_OT_FUNC("%p", text_map);
+
+	if (text_map == NULL)
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_TEXT_MAP(text_map);
+
+	if ((text_map->key != NULL) && (text_map->value != NULL) && (text_map->count > 0)) {
+		size_t i;
+
+		for (i = 0; i < text_map->count; i++)
+			FLT_OT_DBG(3, "  \"%s\" -> \"%s\"", text_map->key[i], text_map->value[i]);
+	}
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   ot_debug -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void ot_debug(void)
+{
+	char buffer[BUFSIZ];
+
+	FLT_OT_FUNC("");
+
+	otc_statistics(buffer, sizeof(buffer));
+	FLT_OT_DBG(0, "%s", buffer);
+
+	FLT_OT_RETURN();
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   ot_mem_malloc -
+ *
+ * ARGUMENTS
+ *   func -
+ *   line -
+ *   size -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static void *ot_mem_malloc(FLT_OT_DBG_ARGS(const char *func, int line, ) size_t size)
+{
+	return flt_ot_pool_alloc(pool_head_ot_span_context, size, 1, NULL);
+}
+
+
+/***
+ * NAME
+ *   ot_mem_free -
+ *
+ * ARGUMENTS
+ *   func -
+ *   line -
+ *   ptr  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void ot_mem_free(FLT_OT_DBG_ARGS(const char *func, int line, ) void *ptr)
+{
+	flt_ot_pool_free(pool_head_ot_span_context, &ptr);
+}
+
+
+/***
+ * NAME
+ *   ot_init -
+ *
+ * ARGUMENTS
+ *   tracer -
+ *   config -
+ *   plugin -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_init(struct otc_tracer **tracer, const char *config, const char *plugin, char **err)
+{
+	char cwd[PATH_MAX], path[PATH_MAX], errbuf[BUFSIZ] = "";
+	int  rc, retval = -1;
+
+	FLT_OT_FUNC("%p:%p \"%s\", \"%s\", %p:%p", FLT_OT_DPTR_ARGS(tracer), config, plugin, FLT_OT_DPTR_ARGS(err));
+
+	flt_ot_pools_info();
+#ifdef USE_POOL_OT_SPAN_CONTEXT
+	FLT_OT_DBG(2, "sizeof_pool(ot_span_context) = %u", pool_head_ot_span_context->size);
+#endif
+
+	if (getcwd(cwd, sizeof(cwd)) == NULL) {
+		FLT_OT_ERR("failed to get current working directory");
+
+		FLT_OT_RETURN(retval);
+	}
+	rc = snprintf(path, sizeof(path), "%s/%s", cwd, plugin);
+	if ((rc == -1) || (rc >= sizeof(path))) {
+		FLT_OT_ERR("failed to construct the OpenTracing plugin path");
+
+		FLT_OT_RETURN(retval);
+	}
+
+	*tracer = otc_tracer_init(path, config, NULL, errbuf, sizeof(errbuf));
+	if (*tracer == NULL) {
+		FLT_OT_ERR("%s", (*errbuf == '\0') ? "failed to initialize tracing library" : errbuf);
+	} else {
+		otc_ext_init(ot_mem_malloc, ot_mem_free);
+
+		retval = 0;
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_close -
+ *
+ * ARGUMENTS
+ *   tracer -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void ot_close(struct otc_tracer **tracer)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(tracer));
+
+	if ((tracer == NULL) || (*tracer == NULL))
+		FLT_OT_RETURN();
+
+	(*tracer)->close(*tracer);
+
+	*tracer = NULL;
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   ot_span_init -
+ *
+ * ARGUMENTS
+ *   tracer         -
+ *   operation_name -
+ *   ts_steady      -
+ *   ts_system      -
+ *   ref_type       -
+ *   ref_ctx_idx    -
+ *   ref_span       -
+ *   tags           -
+ *   num_tags       -
+ *   err            -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span *ot_span_init(struct otc_tracer *tracer, const char *operation_name, const struct timespec *ts_steady, const struct timespec *ts_system, int ref_type, int ref_ctx_idx, const struct otc_span *ref_span, const struct otc_tag *tags, int num_tags, char **err)
+{
+	struct otc_start_span_options  options;
+	struct otc_span_context        context = { .idx = ref_ctx_idx, .span = ref_span };
+	struct otc_span_reference      references = { ref_type, &context };
+	struct otc_span               *retptr = NULL;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p, %d, %d, %p, %p, %d, %p:%p", tracer, operation_name, ts_steady, ts_system, ref_type, ref_ctx_idx, ref_span, tags, num_tags, FLT_OT_DPTR_ARGS(err));
+
+	if (operation_name == NULL)
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	(void)memset(&options, 0, sizeof(options));
+
+	if (ts_steady != NULL)
+		(void)memcpy(&(options.start_time_steady.value), ts_steady, sizeof(options.start_time_steady.value));
+
+	if (ts_system != NULL)
+		(void)memcpy(&(options.start_time_system.value), ts_system, sizeof(options.start_time_system.value));
+
+	if (FLT_OT_IN_RANGE(ref_type, otc_span_reference_child_of, otc_span_reference_follows_from)) {
+		options.references     = &references;
+		options.num_references = 1;
+	}
+
+	options.tags     = tags;
+	options.num_tags = num_tags;
+
+	retptr = tracer->start_span_with_options(tracer, operation_name, &options);
+	if (retptr == NULL)
+		FLT_OT_ERR("failed to init new span");
+	else
+		FLT_OT_DBG(2, "span %p:%zd initialized", retptr, retptr->idx);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_span_init_va -
+ *
+ * ARGUMENTS
+ *   tracer         -
+ *   operation_name -
+ *   ts_steady      -
+ *   ts_system      -
+ *   ref_type       -
+ *   ref_ctx_idx    -
+ *   ref_span       -
+ *   err            -
+ *   tag_key        -
+ *   tag_value      -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span *ot_span_init_va(struct otc_tracer *tracer, const char *operation_name, const struct timespec *ts_steady, const struct timespec *ts_system, int ref_type, int ref_ctx_idx, const struct otc_span *ref_span, char **err, const char *tag_key, const char *tag_value, ...)
+{
+	struct otc_tag   tags[FLT_OT_MAXTAGS];
+	int              num_tags = 0;
+	struct otc_span *retptr;
+
+	FLT_OT_FUNC("%p, \"%s\", %p, %p, %d, %d, %p, %p:%p, \"%s\", \"%s\", ...", tracer, operation_name, ts_steady, ts_system, ref_type, ref_ctx_idx, ref_span, FLT_OT_DPTR_ARGS(err), tag_key, tag_value);
+
+	if (tag_key != NULL) {
+		va_list ap;
+
+		va_start(ap, tag_value);
+		for (num_tags = 0; (num_tags < FLT_OT_TABLESIZE(tags)) && (tag_key != NULL) && (tag_value != NULL); num_tags++) {
+			tags[num_tags].key = (char *)tag_key;
+			FLT_OT_VSET(&(tags[num_tags].value), string, tag_value);
+
+			tag_key = va_arg(ap, typeof(tag_key));
+			if (tag_key != NULL)
+				tag_value = va_arg(ap, typeof(tag_value));
+		}
+		va_end(ap);
+	}
+
+	retptr = ot_span_init(tracer, operation_name, ts_steady, ts_system, ref_type, ref_ctx_idx, ref_span, tags, num_tags, err);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_span_tag -
+ *
+ * ARGUMENTS
+ *   span     -
+ *   tags     -
+ *   num_tags -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_tag(struct otc_span *span, const struct otc_tag *tags, int num_tags)
+{
+	int retval = -1;
+
+	FLT_OT_FUNC("%p, %p, %d", span, tags, num_tags);
+
+	if ((span == NULL) || (tags == NULL))
+		FLT_OT_RETURN(retval);
+
+	for (retval = 0; retval < num_tags; retval++)
+		span->set_tag(span, tags[retval].key, &(tags[retval].value));
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_span_tag_va -
+ *
+ * ARGUMENTS
+ *   span  -
+ *   key   -
+ *   type  -
+ *   value -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_tag_va(struct otc_span *span, const char *key, int type, ...)
+{
+	va_list          ap;
+	struct otc_value ot_value;
+	int              retval = -1;
+
+	FLT_OT_FUNC("%p, \"%s\", %d, ...", span, key, type);
+
+	if ((span == NULL) || (key == NULL))
+		FLT_OT_RETURN(retval);
+
+	va_start(ap, type);
+	for (retval = 0; (key != NULL) && FLT_OT_IN_RANGE(type, otc_value_bool, otc_value_null); retval++) {
+		ot_value.type = type;
+		if (type == otc_value_bool)
+			ot_value.value.bool_value = va_arg(ap, typeof(ot_value.value.bool_value));
+		else if (type == otc_value_double)
+			ot_value.value.double_value = va_arg(ap, typeof(ot_value.value.double_value));
+		else if (type == otc_value_int64)
+			ot_value.value.int64_value = va_arg(ap, typeof(ot_value.value.int64_value));
+		else if (type == otc_value_uint64)
+			ot_value.value.uint64_value = va_arg(ap, typeof(ot_value.value.uint64_value));
+		else if (type == otc_value_string)
+			ot_value.value.string_value = va_arg(ap, typeof(ot_value.value.string_value));
+		else if (type == otc_value_null)
+			ot_value.value.string_value = va_arg(ap, typeof(ot_value.value.string_value));
+		span->set_tag(span, key, &ot_value);
+
+		key = va_arg(ap, typeof(key));
+		if (key != NULL)
+			type = va_arg(ap, typeof(type));
+	}
+	va_end(ap);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_span_log -
+ *
+ * ARGUMENTS
+ *   span       -
+ *   log_fields -
+ *   num_fields -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_log(struct otc_span *span, const struct otc_log_field *log_fields, int num_fields)
+{
+	int retval = -1;
+
+	FLT_OT_FUNC("%p, %p, %d", span, log_fields, num_fields);
+
+	if ((span == NULL) || (log_fields == NULL))
+		FLT_OT_RETURN(retval);
+
+	retval = MIN(OTC_MAXLOGFIELDS, num_fields);
+
+	span->log_fields(span, log_fields, retval);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_span_log_va -
+ *
+ * ARGUMENTS
+ *   span  -
+ *   key   -
+ *   value -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_log_va(struct otc_span *span, const char *key, const char *value, ...)
+{
+	va_list              ap;
+	struct otc_log_field log_field[OTC_MAXLOGFIELDS];
+	int                  retval = -1;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", ...", span, key, value);
+
+	if ((span == NULL) || (key == NULL) || (value == NULL))
+		FLT_OT_RETURN(retval);
+
+	va_start(ap, value);
+	for (retval = 0; (retval < FLT_OT_TABLESIZE(log_field)) && (key != NULL); retval++) {
+		log_field[retval].key                      = key;
+		log_field[retval].value.type               = otc_value_string;
+		log_field[retval].value.value.string_value = value;
+
+		key = va_arg(ap, typeof(key));
+		if (key != NULL)
+			value = va_arg(ap, typeof(value));
+	}
+	va_end(ap);
+
+	span->log_fields(span, log_field, retval);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_span_log_fmt -
+ *
+ * ARGUMENTS
+ *   span   -
+ *   key    -
+ *   format -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_log_fmt(struct otc_span *span, const char *key, const char *format, ...)
+{
+	va_list ap;
+	char    value[BUFSIZ];
+	int     n;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", ...", span, key, format);
+
+	if ((span == NULL) || (key == NULL) || (format == NULL))
+		FLT_OT_RETURN(-1);
+
+	va_start(ap, format);
+	n = vsnprintf(value, sizeof(value), format, ap);
+	if (!FLT_OT_IN_RANGE(n, 0, sizeof(value) - 1)) {
+		FLT_OT_DBG(2, "WARNING: log buffer too small (%d > %zu)", n, sizeof(value));
+
+		FLT_OT_STR_ELLIPSIS(value, sizeof(value));
+	}
+	va_end(ap);
+
+	FLT_OT_RETURN(ot_span_log_va(span, key, value, NULL));
+}
+
+
+/***
+ * NAME
+ *   ot_span_set_baggage -
+ *
+ * ARGUMENTS
+ *   span    -
+ *   baggage -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_set_baggage(struct otc_span *span, const struct otc_text_map *baggage)
+{
+	size_t i;
+	int    retval = -1;
+
+	FLT_OT_FUNC("%p, %p", span, baggage);
+
+	if ((span == NULL) || (baggage == NULL))
+		FLT_OT_RETURN(retval);
+
+	if ((baggage->key == NULL) || (baggage->value == NULL))
+		FLT_OT_RETURN(retval);
+
+	for (retval = i = 0; i < baggage->count; i++) {
+		FLT_OT_DBG(3, "set baggage: \"%s\" -> \"%s\"", baggage->key[i], baggage->value[i]);
+
+		if ((baggage->key[i] != NULL) && (baggage->value[i] != NULL)) {
+			span->set_baggage_item(span, baggage->key[i], baggage->value[i]);
+
+			retval++;
+		}
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_span_set_baggage_va -
+ *
+ * ARGUMENTS
+ *   span  -
+ *   key   -
+ *   value -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int ot_span_set_baggage_va(struct otc_span *span, const char *key, const char *value, ...)
+{
+	va_list ap;
+	int     retval = -1;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", ...", span, key, value);
+
+	if ((span == NULL) || (key == NULL) || (value == NULL))
+		FLT_OT_RETURN(retval);
+
+	va_start(ap, value);
+	for (retval = 0; (key != NULL); retval++) {
+		FLT_OT_DBG(3, "set baggage: \"%s\" -> \"%s\"", key, value);
+
+		span->set_baggage_item(span, key, value);
+
+		key = va_arg(ap, typeof(key));
+		if (key != NULL)
+			value = va_arg(ap, typeof(value));
+	}
+	va_end(ap);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   ot_span_baggage_va -
+ *
+ * ARGUMENTS
+ *   span -
+ *   key  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_text_map *ot_span_baggage_va(const struct otc_span *span, const char *key, ...)
+{
+	va_list              ap;
+	struct otc_text_map *retptr = NULL;
+	int                  i, n;
+
+	FLT_OT_FUNC("%p, \"%s\", ...", span, key);
+
+	if ((span == NULL) || (key == NULL))
+		FLT_OT_RETURN(retptr);
+
+	va_start(ap, key);
+	for (n = 1; va_arg(ap, typeof(key)) != NULL; n++);
+	va_end(ap);
+
+	retptr = otc_text_map_new(NULL, n);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	va_start(ap, key);
+	for (i = 0; (i < n) && (key != NULL); i++) {
+		char *value = (char *)span->baggage_item(span, key);
+
+		if (value != NULL) {
+			(void)otc_text_map_add(retptr, key, 0, value, 0, OTC_TEXT_MAP_DUP_KEY | OTC_TEXT_MAP_DUP_VALUE);
+
+			FLT_OT_DBG(3, "get baggage[%d]: \"%s\" -> \"%s\"", i, retptr->key[i], retptr->value[i]);
+		} else {
+			FLT_OT_DBG(3, "get baggage[%d]: \"%s\" -> invalid key", i, key);
+		}
+
+		key = va_arg(ap, typeof(key));
+	}
+	va_end(ap);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_inject_text_map -
+ *
+ * ARGUMENTS
+ *   tracer  -
+ *   span    -
+ *   carrier -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span_context *ot_inject_text_map(struct otc_tracer *tracer, const struct otc_span *span, struct otc_text_map_writer *carrier)
+{
+	struct otc_span_context *retptr = NULL;
+	int                      rc;
+
+	FLT_OT_FUNC("%p, %p, %p", tracer, span, carrier);
+
+	if ((span == NULL) || (carrier == NULL))
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr = span->span_context((struct otc_span *)span);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	(void)memset(carrier, 0, sizeof(*carrier));
+
+	rc = tracer->inject_text_map(tracer, carrier, retptr);
+	if (rc != otc_propagation_error_code_success) {
+		FLT_OT_FREE_CLEAR(retptr);
+	} else {
+#ifdef DEBUG_OT
+		FLT_OT_DBG_TEXT_CARRIER(carrier, set);
+		ot_text_map_show(&(carrier->text_map));
+		FLT_OT_DBG_SPAN_CONTEXT(retptr);
+#endif
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_inject_http_headers -
+ *
+ * ARGUMENTS
+ *   tracer  -
+ *   span    -
+ *   carrier -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span_context *ot_inject_http_headers(struct otc_tracer *tracer, const struct otc_span *span, struct otc_http_headers_writer *carrier, char **err)
+{
+	struct otc_span_context *retptr = NULL;
+	int                      rc;
+
+	FLT_OT_FUNC("%p, %p, %p, %p:%p", tracer, span, carrier, FLT_OT_DPTR_ARGS(err));
+
+	if ((span == NULL) || (carrier == NULL))
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr = span->span_context((struct otc_span *)span);
+	if (retptr == NULL) {
+		FLT_OT_ERR("failed to create span context");
+
+		FLT_OT_RETURN(retptr);
+	}
+
+	(void)memset(carrier, 0, sizeof(*carrier));
+
+	rc = tracer->inject_http_headers(tracer, carrier, retptr);
+	if (rc != otc_propagation_error_code_success) {
+		FLT_OT_ERR("failed to inject HTTP headers data");
+
+		FLT_OT_FREE_CLEAR(retptr);
+	} else {
+#ifdef DEBUG_OT
+		FLT_OT_DBG_TEXT_CARRIER(carrier, set);
+		ot_text_map_show(&(carrier->text_map));
+		FLT_OT_DBG_SPAN_CONTEXT(retptr);
+#endif
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_inject_binary -
+ *
+ * ARGUMENTS
+ *   tracer  -
+ *   span    -
+ *   carrier -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span_context *ot_inject_binary(struct otc_tracer *tracer, const struct otc_span *span, struct otc_custom_carrier_writer *carrier)
+{
+	struct otc_span_context *retptr = NULL;
+	int                      rc;
+
+	FLT_OT_FUNC("%p, %p, %p", tracer, span, carrier);
+
+	if ((span == NULL) || (carrier == NULL))
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr = span->span_context((struct otc_span *)span);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	(void)memset(carrier, 0, sizeof(*carrier));
+
+	rc = tracer->inject_binary(tracer, carrier, retptr);
+	if (rc != otc_propagation_error_code_success) {
+		FLT_OT_FREE_CLEAR(retptr);
+	} else {
+#ifdef DEBUG_OT
+		struct otc_jaeger_trace_context *ctx = carrier->binary_data.data;
+
+		FLT_OT_DBG_CUSTOM_CARRIER(carrier, inject);
+		FLT_OT_DBG(3, "trace context: %016" PRIx64 "%016" PRIx64 ":%016" PRIx64 ":%016" PRIx64 ":%02hhx <%s> <%s>",
+		           ctx->trace_id[0], ctx->trace_id[1], ctx->span_id, ctx->parent_span_id, ctx->flags,
+		           flt_ot_str_hex(ctx->baggage, carrier->binary_data.size - sizeof(*ctx)),
+		           flt_ot_str_ctrl(ctx->baggage, carrier->binary_data.size - sizeof(*ctx)));
+		FLT_OT_DBG_SPAN_CONTEXT(retptr);
+#endif
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_extract_text_map -
+ *
+ * ARGUMENTS
+ *   tracer   -
+ *   carrier  -
+ *   text_map -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span_context *ot_extract_text_map(struct otc_tracer *tracer, struct otc_text_map_reader *carrier, const struct otc_text_map *text_map)
+{
+	struct otc_span_context *retptr = NULL;
+	int                      rc;
+
+	FLT_OT_FUNC("%p, %p, %p", tracer, carrier, text_map);
+
+	if (carrier == NULL)
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	if (text_map != NULL) {
+		(void)memset(carrier, 0, sizeof(*carrier));
+		(void)memcpy(&(carrier->text_map), text_map, sizeof(carrier->text_map));
+
+		FLT_OT_DBG_TEXT_CARRIER(carrier, foreach_key);
+	}
+
+	rc = tracer->extract_text_map(tracer, carrier, &retptr);
+	if (rc != otc_propagation_error_code_success)
+		FLT_OT_FREE_CLEAR(retptr);
+	else if (retptr != NULL)
+		FLT_OT_DBG_SPAN_CONTEXT(retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_extract_http_headers -
+ *
+ * ARGUMENTS
+ *   tracer   -
+ *   carrier  -
+ *   text_map -
+ *   err      -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span_context *ot_extract_http_headers(struct otc_tracer *tracer, struct otc_http_headers_reader *carrier, const struct otc_text_map *text_map, char **err)
+{
+	struct otc_span_context *retptr = NULL;
+	int                      rc;
+
+	FLT_OT_FUNC("%p, %p, %p, %p:%p", tracer, carrier, text_map, FLT_OT_DPTR_ARGS(err));
+
+	if (carrier == NULL)
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	if (text_map != NULL) {
+		(void)memset(carrier, 0, sizeof(*carrier));
+		(void)memcpy(&(carrier->text_map), text_map, sizeof(carrier->text_map));
+
+		FLT_OT_DBG_TEXT_CARRIER(carrier, foreach_key);
+	}
+
+	rc = tracer->extract_http_headers(tracer, carrier, &retptr);
+	if (rc != otc_propagation_error_code_success) {
+		FLT_OT_ERR("failed to extract HTTP headers data");
+
+		FLT_OT_FREE_CLEAR(retptr);
+	}
+	else if (retptr != NULL)
+		FLT_OT_DBG_SPAN_CONTEXT(retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_extract_binary -
+ *
+ * ARGUMENTS
+ *   tracer      -
+ *   carrier     -
+ *   binary_data -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_span_context *ot_extract_binary(struct otc_tracer *tracer, struct otc_custom_carrier_reader *carrier, const struct otc_binary_data *binary_data)
+{
+	struct otc_span_context *retptr = NULL;
+	int                      rc;
+
+	FLT_OT_FUNC("%p, %p, %p", tracer, carrier, binary_data);
+
+	if (carrier == NULL)
+		FLT_OT_RETURN(retptr);
+	else if (tracer == NULL)
+		FLT_OT_RETURN(retptr);
+
+	if ((FLT_OT_DEREF(binary_data, data, NULL) != NULL) && (binary_data->size > 0)) {
+		(void)memset(carrier, 0, sizeof(*carrier));
+		(void)memcpy(&(carrier->binary_data), binary_data, sizeof(carrier->binary_data));
+
+		FLT_OT_DBG_CUSTOM_CARRIER(carrier, extract);
+	}
+
+	rc = tracer->extract_binary(tracer, carrier, &retptr);
+	if (rc != otc_propagation_error_code_success)
+		FLT_OT_FREE_CLEAR(retptr);
+	else if (retptr != NULL)
+		FLT_OT_DBG_SPAN_CONTEXT(retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   ot_span_finish -
+ *
+ * ARGUMENTS
+ *   span      -
+ *   ts_finish -
+ *   log_ts    -
+ *   log_key   -
+ *   log_value -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void ot_span_finish(struct otc_span **span, const struct timespec *ts_finish, const struct timespec *log_ts, const char *log_key, const char *log_value, ...)
+{
+	struct otc_finish_span_options options;
+	struct otc_log_field           log_field[OTC_MAXLOGFIELDS];
+	struct otc_log_record          log_records = { .fields = log_field, .num_fields = 0 };
+#ifdef DEBUG_OT
+	typeof((*span)->idx)           idx = FLT_OT_DDEREF(span, idx, 0);
+#endif
+
+	FLT_OT_FUNC("%p:%p, %p, %p, \"%s\", \"%s\", ...", FLT_OT_DPTR_ARGS(span), ts_finish, log_ts, log_key, log_value);
+
+	if ((span == NULL) || (*span == NULL))
+		FLT_OT_RETURN();
+
+	(void)memset(&options, 0, sizeof(options));
+
+	if (ts_finish != NULL)
+		(void)memcpy(&(options.finish_time.value), ts_finish, sizeof(options.finish_time.value));
+
+	if (log_key != NULL) {
+		va_list ap;
+		int     i;
+
+		if (log_ts != NULL)
+			(void)memcpy(&(log_records.timestamp.value), log_ts, sizeof(log_records.timestamp.value));
+
+		va_start(ap, log_value);
+		for (i = 0; (i < FLT_OT_TABLESIZE(log_field)) && (log_key != NULL); i++) {
+			log_field[i].key                      = log_key;
+			log_field[i].value.type               = otc_value_string;
+			log_field[i].value.value.string_value = log_value;
+
+			log_key = va_arg(ap, typeof(log_key));
+			if (log_key != NULL)
+				log_value = va_arg(ap, typeof(log_value));
+		}
+		va_end(ap);
+
+		log_records.num_fields  = i;
+		options.log_records     = &log_records;
+		options.num_log_records = 1;
+	}
+
+	/*
+	 * Caution: memory allocated for the span is released
+	 *          in the function finish_with_options().
+	 */
+	(*span)->finish_with_options(*span, &options);
+
+	FLT_OT_DBG(2, "span %p:%zu finished", *span, idx);
+
+	*span = NULL;
+
+	FLT_OT_RETURN();
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/parser.c b/addons/ot/src/parser.c
new file mode 100644
index 0000000..b53d58d
--- /dev/null
+++ b/addons/ot/src/parser.c
@@ -0,0 +1,1216 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+#ifdef DEBUG_OT
+struct flt_ot_debug               flt_ot_debug;
+THREAD_LOCAL int                  dbg_indent_level = 0;
+#endif
+
+#ifdef OTC_DBG_MEM
+static struct otc_dbg_mem_data    dbg_mem_data[1000000];
+static struct otc_dbg_mem         dbg_mem;
+#endif
+
+static struct flt_ot_conf        *flt_ot_current_config = NULL;
+static struct flt_ot_conf_tracer *flt_ot_current_tracer = NULL;
+static struct flt_ot_conf_group  *flt_ot_current_group = NULL;
+static struct flt_ot_conf_scope  *flt_ot_current_scope = NULL;
+static struct flt_ot_conf_span   *flt_ot_current_span = NULL;
+
+
+/***
+ * NAME
+ *   flt_ot_parse_strdup -
+ *
+ * ARGUMENTS
+ *   ptr     -
+ *   str     -
+ *   err     -
+ *   err_msg -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_strdup(char **ptr, const char *str, char **err, const char *err_msg)
+{
+	int retval = ERR_NONE;
+
+	FLT_OT_FUNC("%p:%p, %p, %p:%p, \"%s\"", FLT_OT_DPTR_ARGS(ptr), str, FLT_OT_DPTR_ARGS(err), err_msg);
+
+	*ptr = FLT_OT_STRDUP(str);
+	if (*ptr == NULL) {
+		FLT_OT_PARSE_ERR(err, "'%s' : out of memory", err_msg);
+
+		retval |= ERR_ABORT | ERR_ALERT;
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_keyword -
+ *
+ * ARGUMENTS
+ *   ptr     -
+ *   args    -
+ *   cur_arg -
+ *   pos     -
+ *   err     -
+ *   err_msg -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_keyword(char **ptr, char **args, int cur_arg, int pos, char **err, const char *err_msg)
+{
+	int retval = ERR_NONE;
+
+	FLT_OT_FUNC("%p:%p, %p, %d, %d, %p:%p, \"%s\"", FLT_OT_DPTR_ARGS(ptr), args, cur_arg, pos, FLT_OT_DPTR_ARGS(err), err_msg);
+
+	if (*ptr != NULL) {
+		if (cur_arg == pos)
+			FLT_OT_PARSE_ERR(err, FLT_OT_FMT_TYPE "%s already set", err_msg);
+		else
+			FLT_OT_PARSE_ERR(err, "'%s' : %s already set", args[cur_arg], err_msg);
+	}
+	else if (!FLT_OT_ARG_ISVALID(pos + 1)) {
+		if (cur_arg == pos)
+			FLT_OT_PARSE_ERR(err, FLT_OT_FMT_TYPE "no %s set", err_msg);
+		else
+			FLT_OT_PARSE_ERR(err, "'%s' : no %s set", args[cur_arg], err_msg);
+	}
+	else {
+		retval = flt_ot_parse_strdup(ptr, args[pos + 1], err, args[cur_arg]);
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_invalid_char -
+ *
+ * ARGUMENTS
+ *   name -
+ *   type -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static const char *flt_ot_parse_invalid_char(const char *name, int type)
+{
+	const char *retptr = NULL;
+
+	FLT_OT_FUNC("\"%s\", %d", name, type);
+
+	if (!FLT_OT_STR_ISVALID(name))
+		FLT_OT_RETURN(retptr);
+
+	if (type == 1) {
+		retptr = invalid_char(name);
+	}
+	else if (type == 2) {
+		retptr = invalid_domainchar(name);
+	}
+	else if (type == 3) {
+		retptr = invalid_prefix_char(name);
+	}
+	else if (type == 4) {
+		retptr = name;
+
+		/*
+		 * Allowed characters are letters, numbers and '_', the first
+		 * character in the string must not be a number.
+		 */
+		if (!isdigit(*retptr))
+			for (++retptr; (*retptr == '_') || isalnum(*retptr); retptr++);
+
+		if (*retptr == '\0')
+			retptr = NULL;
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_check -
+ *
+ * ARGUMENTS
+ *   file            -
+ *   linenum         -
+ *   args            -
+ *   id              -
+ *   parse_data      -
+ *   parse_data_size -
+ *   pdata           -
+ *   err             -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_check(const char *file, int linenum, char **args, const void *id, const struct flt_ot_parse_data *parse_data, size_t parse_data_size, const struct flt_ot_parse_data **pdata, char **err)
+{
+	int i, retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p, %p, %zu, %p:%p, %p:%p", file, linenum, args, id, parse_data, parse_data_size, FLT_OT_DPTR_ARGS(pdata), FLT_OT_DPTR_ARGS(err));
+
+	FLT_OT_ARGS_DUMP();
+
+	*pdata = NULL;
+
+	for (i = 0; (*pdata == NULL) && (i < parse_data_size); i++)
+		if (strcmp(parse_data[i].name, args[0]) == 0)
+			*pdata = parse_data + i;
+
+	if (*pdata == NULL)
+		FLT_OT_PARSE_ERR(err, "'%s' : unknown keyword", args[0]);
+
+	if ((retval & ERR_CODE) || (id == NULL))
+		/* Do nothing. */;
+	else if ((id != flt_ot_current_tracer) && (flt_ot_current_config->tracer == NULL))
+		FLT_OT_PARSE_ERR(err, "tracer not defined");
+
+	/*
+	 * Checking that fewer arguments are specified in the configuration
+	 * line than is required.
+	 */
+	if (!(retval & ERR_CODE))
+		for (i = 1; i < (*pdata)->args_min; i++)
+			if (!FLT_OT_ARG_ISVALID(i))
+				FLT_OT_PARSE_ERR(err, "'%s' : too few arguments (use '%s%s')", args[0], (*pdata)->name, (*pdata)->usage);
+
+	/*
+	 * Checking that more arguments are specified in the configuration
+	 * line than the maximum allowed.
+	 */
+	if (!(retval & ERR_CODE) && ((*pdata)->args_max > 0)) {
+		for ( ; (i <= (*pdata)->args_max) && FLT_OT_ARG_ISVALID(i); i++);
+
+		if (i > (*pdata)->args_max)
+			FLT_OT_PARSE_ERR(err, "'%s' : too many arguments (use '%s%s')", args[0], (*pdata)->name, (*pdata)->usage);
+	}
+
+	/* Checking that the first argument has only allowed characters. */
+	if (!(retval & ERR_CODE) && ((*pdata)->check_name > 0)) {
+		const char *ic;
+
+		ic = flt_ot_parse_invalid_char(args[1], (*pdata)->check_name);
+		if (ic != NULL)
+			FLT_OT_PARSE_ERR(err, "%s '%s' : invalid character '%c'", args[0], args[1], *ic);
+	}
+
+	/* Checking that the data group name is defined. */
+	if (!(retval & ERR_CODE) && (*pdata)->flag_check_id && (id == NULL))
+		FLT_OT_PARSE_ERR(err, "'%s' : %s ID not set (use '%s%s')", args[0], parse_data[1].name, parse_data[1].name, parse_data[1].usage);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_sample_expr -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   idx     -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_sample_expr(const char *file, int linenum, char **args, int *idx, struct list *head, char **err)
+{
+	struct flt_ot_conf_sample_expr *expr;
+	int                             retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p, %p, %p:%p", file, linenum, args, idx, head, FLT_OT_DPTR_ARGS(err));
+
+	expr = flt_ot_conf_sample_expr_init(args[*idx], linenum, head, err);
+	if (expr != NULL) {
+		expr->expr = sample_parse_expr(args, idx, file, linenum, err, &(flt_ot_current_config->proxy->conf.args), NULL);
+		if (expr->expr != NULL)
+			FLT_OT_DBG(3, "sample expression '%s' added", expr->value);
+		else
+			retval |= ERR_ABORT | ERR_ALERT;
+	} else {
+			retval |= ERR_ABORT | ERR_ALERT;
+	}
+
+	if (retval & ERR_CODE)
+		flt_ot_conf_sample_expr_free(&expr);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_sample -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_sample(const char *file, int linenum, char **args, struct list *head, char **err)
+{
+	struct flt_ot_conf_sample *sample;
+	int                        idx = 2, retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p, %p:%p", file, linenum, args, head, FLT_OT_DPTR_ARGS(err));
+
+	sample = flt_ot_conf_sample_init(args, linenum, head, err);
+	if (sample == NULL)
+		FLT_OT_PARSE_ERR(err, "'%s' : out of memory", args[0]);
+
+	if (!(retval & ERR_CODE)) {
+		flt_ot_current_config->proxy->conf.args.ctx  = ARGC_OT;
+		flt_ot_current_config->proxy->conf.args.file = file;
+		flt_ot_current_config->proxy->conf.args.line = linenum;
+
+		while (!(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(idx))
+			retval = flt_ot_parse_cfg_sample_expr(file, linenum, args, &idx, &(sample->exprs), err);
+
+		flt_ot_current_config->proxy->conf.args.file = NULL;
+		flt_ot_current_config->proxy->conf.args.line = 0;
+	}
+
+	if (retval & ERR_CODE)
+		flt_ot_conf_sample_free(&sample);
+	else
+		FLT_OT_DBG(3, "sample '%s' -> '%s' added", sample->key, sample->value);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_str -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   head    -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_str(const char *file, int linenum, char **args, struct list *head, char **err)
+{
+	struct flt_ot_conf_str *str = NULL;
+	int                     i, retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p, %p:%p", file, linenum, args, head, FLT_OT_DPTR_ARGS(err));
+
+	for (i = 1; !(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(i); i++)
+		if (flt_ot_conf_str_init(args[i], linenum, head, err) == NULL)
+			retval |= ERR_ABORT | ERR_ALERT;
+
+	if (retval & ERR_CODE)
+		flt_ot_conf_str_free(&str);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_file -
+ *
+ * ARGUMENTS
+ *   ptr     -
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   err     -
+ *   err_msg -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_file(char **ptr, const char *file, int linenum, char **args, char **err, const char *err_msg)
+{
+	int retval = ERR_NONE;
+
+	FLT_OT_FUNC("%p:%p, \"%s\", %d, %p, %p:%p, \"%s\"", FLT_OT_DPTR_ARGS(ptr), file, linenum, args, FLT_OT_DPTR_ARGS(err), err_msg);
+
+	if (!FLT_OT_ARG_ISVALID(1))
+		FLT_OT_PARSE_ERR(err, "'%s' : no %s specified", flt_ot_current_tracer->id, err_msg);
+	else if (alertif_too_many_args(1, file, linenum, args, &retval))
+		retval |= ERR_ABORT | ERR_ALERT;
+	else if (access(args[1], R_OK) == -1)
+		FLT_OT_PARSE_ERR(err, "'%s' : %s", args[1], strerror(errno));
+	else
+		retval = flt_ot_parse_keyword(ptr, args, 0, 0, err, err_msg);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_check_scope -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns TRUE in case the configuration is not in the currently defined
+ *   scope, FALSE otherwise.
+ */
+static bool flt_ot_parse_check_scope(void)
+{
+	bool retval = 0;
+
+	if ((cfg_scope != NULL) && (flt_ot_current_config->id != NULL) && (strcmp(flt_ot_current_config->id, cfg_scope) != 0)) {
+		FLT_OT_DBG(1, "cfg_scope: '%s', id: '%s'", cfg_scope, flt_ot_current_config->id);
+
+		retval = 1;
+	}
+
+	return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_tracer -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   kw_mod  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_tracer(const char *file, int linenum, char **args, int kw_mod)
+{
+#define FLT_OT_PARSE_TRACER_DEF(a,b,c,d,e,f,g)   { FLT_OT_PARSE_TRACER_##a, b, c, d, e, f, g },
+	static const struct flt_ot_parse_data  parse_data[] = { FLT_OT_PARSE_TRACER_DEFINES };
+#undef FLT_OT_PARSE_TRACER_DEF
+	const struct flt_ot_parse_data        *pdata = NULL;
+	char                                  *err = NULL, *err_log = NULL;
+	int                                    i, retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, 0x%08x", file, linenum, args, kw_mod);
+
+	if (flt_ot_parse_check_scope())
+		FLT_OT_RETURN(retval);
+
+	retval = flt_ot_parse_cfg_check(file, linenum, args, flt_ot_current_tracer, parse_data, FLT_OT_TABLESIZE(parse_data), &pdata, &err);
+	if (retval & ERR_CODE) {
+		FLT_OT_PARSE_IFERR_ALERT();
+
+		FLT_OT_RETURN(retval);
+	}
+
+	if (pdata->keyword == FLT_OT_PARSE_TRACER_ID) {
+		if (flt_ot_current_config->tracer != NULL) {
+			FLT_OT_PARSE_ERR(&err, "'%s' : tracer can be defined only once", args[1]);
+		} else {
+			flt_ot_current_tracer = flt_ot_conf_tracer_init(args[1], linenum, &err);
+			if (flt_ot_current_tracer == NULL)
+				retval |= ERR_ABORT | ERR_ALERT;
+		}
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_LOG) {
+		if (parse_logsrv(args, &(flt_ot_current_tracer->proxy_log.logsrvs), kw_mod == KWM_NO, &err_log) == 0) {
+			FLT_OT_PARSE_ERR(&err, "'%s %s ...' : %s", args[0], args[1], err_log);
+			FLT_OT_FREE_CLEAR(err_log);
+
+			retval |= ERR_ABORT | ERR_ALERT;
+		} else {
+			flt_ot_current_tracer->logging |= FLT_OT_LOGGING_ON;
+		}
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_CONFIG) {
+		retval = flt_ot_parse_cfg_file(&(flt_ot_current_tracer->config), file, linenum, args, &err, "configuration file");
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_PLUGIN) {
+		retval = flt_ot_parse_cfg_file(&(flt_ot_current_tracer->plugin), file, linenum, args, &err, "plugin library");
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_GROUPS) {
+		for (i = 1; !(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(i); i++)
+			if (flt_ot_conf_ph_init(args[i], linenum, &(flt_ot_current_tracer->ph_groups), &err) == NULL)
+				retval |= ERR_ABORT | ERR_ALERT;
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_SCOPES) {
+		for (i = 1; !(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(i); i++)
+			if (flt_ot_conf_ph_init(args[i], linenum, &(flt_ot_current_tracer->ph_scopes), &err) == NULL)
+				retval |= ERR_ABORT | ERR_ALERT;
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_ACL) {
+		if (strcasecmp(args[1], "or") == 0)
+			FLT_OT_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
+		else if (parse_acl((const char **)args + 1, &(flt_ot_current_tracer->acls), &err, &(flt_ot_current_config->proxy->conf.args), file, linenum) == NULL)
+			retval |= ERR_ABORT | ERR_ALERT;
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_RATE_LIMIT) {
+		flt_ot_current_tracer->rate_limit = FLT_OT_FLOAT_U32(flt_ot_strtod(args[1], 0.0, FLT_OT_RATE_LIMIT_MAX, &err), FLT_OT_RATE_LIMIT_MAX);
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_OPTION) {
+		if (strcmp(args[1], FLT_OT_PARSE_OPTION_DISABLED) == 0) {
+			flt_ot_current_tracer->flag_disabled = (kw_mod == KWM_NO) ? 0 : 1;
+		}
+		else if (strcmp(args[1], FLT_OT_PARSE_OPTION_HARDERR) == 0) {
+			flt_ot_current_tracer->flag_harderr = (kw_mod == KWM_NO) ? 0 : 1;
+		}
+		else if (strcmp(args[1], FLT_OT_PARSE_OPTION_NOLOGNORM) == 0) {
+			if (kw_mod == KWM_NO)
+				flt_ot_current_tracer->logging &= ~FLT_OT_LOGGING_NOLOGNORM;
+			else
+				flt_ot_current_tracer->logging |= FLT_OT_LOGGING_NOLOGNORM;
+		}
+		else
+			FLT_OT_PARSE_ERR(&err, "'%s' : unknown option '%s'", args[0], args[1]);
+	}
+#ifdef DEBUG_OT
+	else if (pdata->keyword == FLT_OT_PARSE_TRACER_DEBUG_LEVEL) {
+		flt_ot_debug.level = flt_ot_strtoll(args[1], 0, 255, &err);
+	}
+#else
+	else {
+		FLT_OT_PARSE_WARNING("'%s' : keyword ignored", file, linenum, args[0]);
+	}
+#endif
+
+	FLT_OT_PARSE_IFERR_ALERT();
+
+	if ((retval & ERR_CODE) && (flt_ot_current_tracer != NULL))
+		flt_ot_conf_tracer_free(&flt_ot_current_tracer);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_post_parse_cfg_tracer -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_post_parse_cfg_tracer(void)
+{
+	int retval = ERR_NONE;
+
+	FLT_OT_FUNC("");
+
+	if (flt_ot_current_tracer == NULL)
+		FLT_OT_RETURN(retval);
+
+	flt_ot_current_config->tracer = flt_ot_current_tracer;
+
+	if (flt_ot_current_tracer->id == NULL)
+		FLT_OT_RETURN(retval);
+
+	if (flt_ot_current_tracer->config == NULL)
+		FLT_OT_POST_PARSE_ALERT("tracer '%s' has no configuration file specified", flt_ot_current_tracer->cfg_line, flt_ot_current_tracer->id);
+
+	if (flt_ot_current_tracer->plugin == NULL)
+		FLT_OT_POST_PARSE_ALERT("tracer '%s' has no plugin library specified", flt_ot_current_tracer->cfg_line, flt_ot_current_tracer->id);
+
+	flt_ot_current_tracer = NULL;
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_group -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   kw_mod  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_group(const char *file, int linenum, char **args, int kw_mod)
+{
+#define FLT_OT_PARSE_GROUP_DEF(a,b,c,d,e,f,g)   { FLT_OT_PARSE_GROUP_##a, b, c, d, e, f, g },
+	static const struct flt_ot_parse_data  parse_data[] = { FLT_OT_PARSE_GROUP_DEFINES };
+#undef FLT_OT_PARSE_GROUP_DEF
+	const struct flt_ot_parse_data        *pdata = NULL;
+	char                                  *err = NULL;
+	int                                    i, retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, 0x%08x", file, linenum, args, kw_mod);
+
+	if (flt_ot_parse_check_scope())
+		FLT_OT_RETURN(retval);
+
+	retval = flt_ot_parse_cfg_check(file, linenum, args, flt_ot_current_group, parse_data, FLT_OT_TABLESIZE(parse_data), &pdata, &err);
+	if (retval & ERR_CODE) {
+		FLT_OT_PARSE_IFERR_ALERT();
+
+		FLT_OT_RETURN(retval);
+	}
+
+	if (pdata->keyword == FLT_OT_PARSE_GROUP_ID) {
+		flt_ot_current_group = flt_ot_conf_group_init(args[1], linenum, &(flt_ot_current_config->groups), &err);
+		if (flt_ot_current_config == NULL)
+			retval |= ERR_ABORT | ERR_ALERT;
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_GROUP_SCOPES) {
+		for (i = 1; !(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(i); i++)
+			if (flt_ot_conf_ph_init(args[i], linenum, &(flt_ot_current_group->ph_scopes), &err) == NULL)
+				retval |= ERR_ABORT | ERR_ALERT;
+	}
+
+	FLT_OT_PARSE_IFERR_ALERT();
+
+	if ((retval & ERR_CODE) && (flt_ot_current_group != NULL))
+		flt_ot_conf_group_free(&flt_ot_current_group);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_post_parse_cfg_group -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_post_parse_cfg_group(void)
+{
+	int retval = ERR_NONE;
+
+	FLT_OT_FUNC("");
+
+	if (flt_ot_current_group == NULL)
+		FLT_OT_RETURN(retval);
+
+	/* Check that the group has at least one scope defined. */
+	if (LIST_ISEMPTY(&(flt_ot_current_group->ph_scopes)))
+		FLT_OT_POST_PARSE_ALERT("group '%s' has no defined scope(s)", flt_ot_current_group->cfg_line, flt_ot_current_group->id);
+
+	flt_ot_current_group = NULL;
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_scope_ctx -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   cur_arg -
+ *   err     -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_scope_ctx(char **args, int cur_arg, char **err)
+{
+	uint8_t flags = 0;
+	int     retval = ERR_NONE;
+
+	FLT_OT_FUNC("%p, %d, %p:%p", args, cur_arg, FLT_OT_DPTR_ARGS(err));
+
+	if (strcmp(args[cur_arg], FLT_OT_PARSE_CTX_USE_HEADERS) == 0)
+		flags = FLT_OT_CTX_USE_HEADERS;
+	else if (strcmp(args[cur_arg], FLT_OT_PARSE_CTX_USE_VARS) == 0)
+		flags = FLT_OT_CTX_USE_VARS;
+	else
+		FLT_OT_PARSE_ERR(err, "'%s' : invalid context storage type", args[0]);
+
+	if (flags == 0)
+		/* Do nothing. */;
+	else if (flt_ot_current_span->ctx_flags & flags)
+		FLT_OT_PARSE_ERR(err, "'%s' : %s already used", args[0], args[cur_arg]);
+	else
+		flt_ot_current_span->ctx_flags |= flags;
+
+	FLT_OT_DBG(2, "ctx_flags: 0x%02hhx (0x%02hhx)", flt_ot_current_span->ctx_flags, flags);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_acl -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   px      -
+ *   args    -
+ *   err     -
+ *   head    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static struct acl_cond *flt_ot_parse_acl(const char *file, int linenum, struct proxy *px, const char **args, char **err, struct list *head, ...)
+{
+	va_list          ap;
+	int              n = 0;
+	struct acl_cond *retptr = NULL;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, %p, %p:%p, %p, ...", file, linenum, px, args, FLT_OT_DPTR_ARGS(err), head);
+
+	for (va_start(ap, head); (retptr == NULL) && (head != NULL); head = va_arg(ap, typeof(head)), n++) {
+		retptr = build_acl_cond(file, linenum, head, px, args, (n == 0) ? err : NULL);
+		if (retptr != NULL)
+			FLT_OT_DBG(2, "ACL build done, using list %p %d", head, n);
+	}
+	va_end(ap);
+
+	if ((retptr != NULL) && (err != NULL))
+		FLT_OT_FREE_CLEAR(*err);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg_scope -
+ *
+ * ARGUMENTS
+ *   file    -
+ *   linenum -
+ *   args    -
+ *   kw_mod  -
+ *
+ * DESCRIPTION
+ *   Function used to load the scope block configuration.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg_scope(const char *file, int linenum, char **args, int kw_mod)
+{
+#define FLT_OT_PARSE_SCOPE_DEF(a,b,c,d,e,f,g)   { FLT_OT_PARSE_SCOPE_##a, b, c, d, e, f, g },
+	static const struct flt_ot_parse_data  parse_data[] = { FLT_OT_PARSE_SCOPE_DEFINES };
+#undef FLT_OT_PARSE_SCOPE_DEF
+	const struct flt_ot_parse_data        *pdata = NULL;
+	char                                  *err = NULL;
+	int                                    i, retval = ERR_NONE;
+
+	FLT_OT_FUNC("\"%s\", %d, %p, 0x%08x", file, linenum, args, kw_mod);
+
+	if (flt_ot_parse_check_scope())
+		FLT_OT_RETURN(retval);
+
+	retval = flt_ot_parse_cfg_check(file, linenum, args, flt_ot_current_span, parse_data, FLT_OT_TABLESIZE(parse_data), &pdata, &err);
+	if (retval & ERR_CODE) {
+		FLT_OT_PARSE_IFERR_ALERT();
+
+		FLT_OT_RETURN(retval);
+	}
+
+	if (pdata->keyword == FLT_OT_PARSE_SCOPE_ID) {
+		/* Initialization of a new scope. */
+		flt_ot_current_scope = flt_ot_conf_scope_init(args[1], linenum, &(flt_ot_current_config->scopes), &err);
+		if (flt_ot_current_scope == NULL)
+			retval |= ERR_ABORT | ERR_ALERT;
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_SPAN) {
+		/*
+		 * Checking if this is the beginning of the definition of
+		 * a new span.
+		 */
+		if (flt_ot_current_span != NULL) {
+			FLT_OT_DBG(3, "span '%s' (done)", flt_ot_current_span->id);
+
+			flt_ot_current_span = NULL;
+		}
+
+		/* Initialization of a new span. */
+		flt_ot_current_span = flt_ot_conf_span_init(args[1], linenum, &(flt_ot_current_scope->spans), &err);
+
+		/*
+		 * In case the span has a defined reference,
+		 * the correctness of the arguments is checked here.
+		 */
+		if (flt_ot_current_span == NULL) {
+			retval |= ERR_ABORT | ERR_ALERT;
+		}
+		else if (FLT_OT_ARG_ISVALID(2)) {
+			for (i = 2; (i < pdata->args_max) && FLT_OT_ARG_ISVALID(i); i++)
+				if (strcmp(args[i], FLT_OT_PARSE_SPAN_ROOT) == 0) {
+					if (flt_ot_current_span->flag_root)
+						FLT_OT_PARSE_ERR(&err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+					else
+						flt_ot_current_span->flag_root = 1;
+				}
+				else if ((strcmp(args[i], FLT_OT_PARSE_SPAN_REF_CHILD) == 0) || (strcmp(args[i], FLT_OT_PARSE_SPAN_REF_FOLLOWS) == 0)) {
+					if (!FLT_OT_ARG_ISVALID(i + 1)) {
+						FLT_OT_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+					}
+					else if (strcmp(args[i++], FLT_OT_PARSE_SPAN_REF_CHILD) == 0) {
+						flt_ot_current_span->ref_type   = otc_span_reference_child_of;
+						flt_ot_current_span->ref_id_len = strlen(args[i]);
+
+						retval = flt_ot_parse_strdup(&(flt_ot_current_span->ref_id), args[i], &err, args[1]);
+					}
+					else {
+						flt_ot_current_span->ref_type   = otc_span_reference_follows_from;
+						flt_ot_current_span->ref_id_len = strlen(args[i]);
+
+						retval = flt_ot_parse_strdup(&(flt_ot_current_span->ref_id), args[i], &err, args[1]);
+					}
+				}
+				else {
+					FLT_OT_PARSE_ERR(&err, "'%s' : invalid argument (use '%s%s')", args[i], pdata->name, pdata->usage);
+				}
+		}
+		else {
+			/*
+			 * This is not a faulty configuration, only such a case
+			 * will be logged.
+			 */
+			FLT_OT_DBG(3, "new span '%s' without reference", flt_ot_current_span->id);
+		}
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_TAG) {
+		retval = flt_ot_parse_cfg_sample(file, linenum, args, &(flt_ot_current_span->tags), &err);
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_LOG) {
+		retval = flt_ot_parse_cfg_sample(file, linenum, args, &(flt_ot_current_span->logs), &err);
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_BAGGAGE) {
+		retval = flt_ot_parse_cfg_sample(file, linenum, args, &(flt_ot_current_span->baggages), &err);
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_INJECT) {
+		/*
+		 * Automatic context name generation can be specified here
+		 * if the contents of the FLT_OT_PARSE_CTX_AUTONAME macro
+		 * are used as the name.  In that case, if the context is
+		 * after a particular event, it gets its name; otherwise
+		 * it gets the name of the current span.
+		 */
+		if (flt_ot_current_span->ctx_id != NULL)
+			FLT_OT_PARSE_ERR(&err, "'%s' : only one context per span is allowed", args[1]);
+		else if (strcmp(args[1], FLT_OT_PARSE_CTX_AUTONAME) != 0)
+			retval = flt_ot_parse_strdup(&(flt_ot_current_span->ctx_id), args[1], &err, args[0]);
+		else if (flt_ot_current_scope->event != FLT_OT_EVENT_REQ_NONE)
+			retval = flt_ot_parse_strdup(&(flt_ot_current_span->ctx_id), flt_ot_event_data[flt_ot_current_scope->event].name, &err, args[0]);
+		else
+			retval = flt_ot_parse_strdup(&(flt_ot_current_span->ctx_id), flt_ot_current_span->id, &err, args[0]);
+
+		if (flt_ot_current_span->ctx_id != NULL) {
+			flt_ot_current_span->ctx_id_len = strlen(flt_ot_current_span->ctx_id);
+
+			/*
+			 * Here is checked the context storage type; which, if
+			 * not explicitly specified, is set to HTTP headers.
+			 *
+			 * It is possible to use both types of context storage
+			 * at the same time.
+			 */
+			if (FLT_OT_ARG_ISVALID(2)) {
+				retval = flt_ot_parse_cfg_scope_ctx(args, 2, &err);
+				if (!(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(3))
+					retval = flt_ot_parse_cfg_scope_ctx(args, 3, &err);
+			} else {
+				flt_ot_current_span->ctx_flags = FLT_OT_CTX_USE_HEADERS;
+			}
+		}
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_EXTRACT) {
+		struct flt_ot_conf_context *conf_ctx;
+
+		/*
+		 * Here is checked the context storage type; which, if
+		 * not explicitly specified, is set to HTTP headers.
+		 */
+		conf_ctx = flt_ot_conf_context_init(args[1], linenum, &(flt_ot_current_scope->contexts), &err);
+		if (conf_ctx == NULL)
+			retval |= ERR_ABORT | ERR_ALERT;
+		else if (!FLT_OT_ARG_ISVALID(2))
+			conf_ctx->flags = FLT_OT_CTX_USE_HEADERS;
+		else if (strcmp(args[2], FLT_OT_PARSE_CTX_USE_HEADERS) == 0)
+			conf_ctx->flags = FLT_OT_CTX_USE_HEADERS;
+		else if (strcmp(args[2], FLT_OT_PARSE_CTX_USE_VARS) == 0)
+			conf_ctx->flags = FLT_OT_CTX_USE_VARS;
+		else
+			FLT_OT_PARSE_ERR(&err, "'%s' : invalid context storage type", args[2]);
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_FINISH) {
+		retval = flt_ot_parse_cfg_str(file, linenum, args, &(flt_ot_current_scope->finish), &err);
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_ACL) {
+		if (strcasecmp(args[1], "or") == 0)
+			FLT_OT_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
+		else if (parse_acl((const char **)args + 1, &(flt_ot_current_scope->acls), &err, &(flt_ot_current_config->proxy->conf.args), file, linenum) == NULL)
+			retval |= ERR_ABORT | ERR_ALERT;
+	}
+	else if (pdata->keyword == FLT_OT_PARSE_SCOPE_EVENT) {
+		/* Scope can only have one event defined. */
+		if (flt_ot_current_scope->event != FLT_OT_EVENT_REQ_NONE) {
+			FLT_OT_PARSE_ERR(&err, "'%s' : event already set", flt_ot_current_scope->id);
+		} else {
+			/* Check the event name. */
+			for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_event_data); i++)
+				if (strcmp(flt_ot_event_data[i].name, args[1]) == 0) {
+					flt_ot_current_scope->event = i;
+
+					break;
+				}
+
+			/*
+			 * The event can have some condition defined and this
+			 * is checked here.
+			 */
+			if (flt_ot_current_scope->event == FLT_OT_EVENT_REQ_NONE) {
+				FLT_OT_PARSE_ERR(&err, "'%s' : unknown event", args[1]);
+			}
+			else if (!FLT_OT_ARG_ISVALID(2)) {
+				/* Do nothing. */
+			}
+			else if ((strcmp(args[2], FLT_OT_CONDITION_IF) == 0) || (strcmp(args[2], FLT_OT_CONDITION_UNLESS) == 0)) {
+				/*
+				 * We will first try to build ACL condition using
+				 * local settings and then if that fails, using
+				 * global settings (from tracer block).  If it
+				 * also fails, then try to use ACL defined in
+				 * the HAProxy configuration.
+				 */
+				flt_ot_current_scope->cond = flt_ot_parse_acl(file, linenum, flt_ot_current_config->proxy, (const char **)args + 2, &err, &(flt_ot_current_scope->acls), &(flt_ot_current_config->tracer->acls), &(flt_ot_current_config->proxy->acl), NULL);
+				if (flt_ot_current_scope->cond == NULL)
+					retval |= ERR_ABORT | ERR_ALERT;
+			}
+			else {
+				FLT_OT_PARSE_ERR(&err, "'%s' : expects either 'if' or 'unless' followed by a condition but found '%s'", args[1], args[2]);
+			}
+
+			if (!(retval & ERR_CODE))
+				FLT_OT_DBG(3, "event '%s'", args[1]);
+		}
+	}
+
+	FLT_OT_PARSE_IFERR_ALERT();
+
+	if ((retval & ERR_CODE) && (flt_ot_current_scope != NULL)) {
+		flt_ot_conf_scope_free(&flt_ot_current_scope);
+
+		flt_ot_current_span = NULL;
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_post_parse_cfg_scope -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   In this function the correctness of the complete scope block is examined.
+ *   This does not mean that all elements are checked here, but only those for
+ *   which it has not been possible to establish their complete correctness in
+ *   the function flt_ot_parse_cfg_scope().
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_post_parse_cfg_scope(void)
+{
+	struct flt_ot_conf_span *conf_span;
+	int                      retval = ERR_NONE;
+
+	FLT_OT_FUNC("");
+
+	if (flt_ot_current_scope == NULL)
+		FLT_OT_RETURN(retval);
+
+	/* If span context inject is used, check that this is possible. */
+	list_for_each_entry(conf_span, &(flt_ot_current_scope->spans), list)
+		if ((conf_span->ctx_id != NULL) && (conf_span->ctx_flags & FLT_OT_CTX_USE_HEADERS))
+			if (!flt_ot_event_data[flt_ot_current_scope->event].flag_http_inject)
+				FLT_OT_POST_PARSE_ALERT("inject '%s' : cannot use on this event", conf_span->cfg_line, conf_span->ctx_id);
+
+	if (retval & ERR_CODE)
+		flt_ot_conf_scope_free(&flt_ot_current_scope);
+
+	flt_ot_current_scope = NULL;
+	flt_ot_current_span  = NULL;
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse_cfg -
+ *
+ * ARGUMENTS
+ *   conf     -
+ *   flt_name -
+ *   err      -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse_cfg(struct flt_ot_conf *conf, const char *flt_name, char **err)
+{
+	struct list backup_sections;
+	int         retval = ERR_ABORT | ERR_ALERT;
+
+	FLT_OT_FUNC("%p, \"%s\", %p:%p", conf, flt_name, FLT_OT_DPTR_ARGS(err));
+
+	flt_ot_current_config = conf;
+
+	/* Backup sections. */
+	LIST_INIT(&backup_sections);
+	cfg_backup_sections(&backup_sections);
+
+	/* Register new OT sections and parse the OT filter configuration file. */
+	if (!cfg_register_section(FLT_OT_PARSE_SECTION_TRACER_ID, flt_ot_parse_cfg_tracer, flt_ot_post_parse_cfg_tracer))
+		/* Do nothing. */;
+	else if (!cfg_register_section(FLT_OT_PARSE_SECTION_GROUP_ID, flt_ot_parse_cfg_group, flt_ot_post_parse_cfg_group))
+		/* Do nothing. */;
+	else if (!cfg_register_section(FLT_OT_PARSE_SECTION_SCOPE_ID, flt_ot_parse_cfg_scope, flt_ot_post_parse_cfg_scope))
+		/* Do nothing. */;
+	else if (access(conf->cfg_file, R_OK) == -1)
+		FLT_OT_PARSE_ERR(err, "'%s' : %s", conf->cfg_file, strerror(errno));
+	else
+		retval = readcfgfile(conf->cfg_file);
+
+	/* Unregister OT sections and restore previous sections. */
+	cfg_unregister_sections();
+	cfg_restore_sections(&backup_sections);
+
+	flt_ot_current_config = NULL;
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_parse -
+ *
+ * ARGUMENTS
+ *   args    -
+ *   cur_arg -
+ *   px      -
+ *   fconf   -
+ *   err     -
+ *   private -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_ot_parse(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private)
+{
+	struct flt_ot_conf *conf = NULL;
+	int                 pos, retval = ERR_NONE;
+
+#ifdef DEBUG_OT
+	FLT_OT_RUN_ONCE(
+#  ifndef DEBUG_OT_SYSTIME
+		(void)memcpy(&(flt_ot_debug.start), &now, sizeof(flt_ot_debug.start));
+#  endif
+
+		flt_ot_debug.level = FLT_OT_DEBUG_LEVEL;
+	);
+#endif
+
+	FLT_OT_FUNC("%p, %p, %p, %p, %p:%p, %p", args, cur_arg, px, fconf, FLT_OT_DPTR_ARGS(err), private);
+
+#ifdef OTC_DBG_MEM
+	FLT_OT_RUN_ONCE(
+		if (otc_dbg_mem_init(&dbg_mem, dbg_mem_data, FLT_OT_TABLESIZE(dbg_mem_data), 0xff) == -1) {
+			FLT_OT_PARSE_ERR(err, "cannot initialize memory debugger");
+
+			FLT_OT_RETURN(retval);
+		}
+	);
+#endif
+
+	FLT_OT_ARGS_DUMP();
+
+	conf = flt_ot_conf_init(px);
+	if (conf == NULL) {
+		FLT_OT_PARSE_ERR(err, "'%s' : out of memory", args[*cur_arg]);
+
+		FLT_OT_RETURN(retval);
+	}
+
+	for (pos = *cur_arg + 1; !(retval & ERR_CODE) && FLT_OT_ARG_ISVALID(pos); pos++) {
+		FLT_OT_DBG(3, "args[%d:2] : { '%s' '%s' }", pos, args[pos], args[pos + 1]);
+
+		if (strcmp(args[pos], FLT_OT_OPT_FILTER_ID) == 0) {
+			retval = flt_ot_parse_keyword(&(conf->id), args, *cur_arg, pos, err, "name");
+			pos++;
+		}
+		else if (strcmp(args[pos], FLT_OT_OPT_CONFIG) == 0) {
+			retval = flt_ot_parse_keyword(&(conf->cfg_file), args, *cur_arg, pos, err, "configuration file");
+			if (!(retval & ERR_CODE))
+				retval = flt_ot_parse_cfg(conf, args[*cur_arg], err);
+			pos++;
+		}
+		else {
+			FLT_OT_PARSE_ERR(err, "'%s' : unknown keyword '%s'", args[*cur_arg], args[pos]);
+		}
+	}
+
+	/* If the OpenTracing filter ID is not set, use default name. */
+	if (!(retval & ERR_CODE) && (conf->id == NULL)) {
+		ha_warning("parsing : " FLT_OT_FMT_TYPE FLT_OT_FMT_NAME "'no filter id set, using default id '%s'\n", FLT_OT_OPT_FILTER_ID_DEFAULT);
+
+		retval = flt_ot_parse_strdup(&(conf->id), FLT_OT_OPT_FILTER_ID_DEFAULT, err, args[*cur_arg]);
+	}
+
+	if (!(retval & ERR_CODE) && (conf->cfg_file == NULL))
+		FLT_OT_PARSE_ERR(err, "'%s' : no configuration file specified", args[*cur_arg]);
+
+	if (retval & ERR_CODE) {
+		flt_ot_conf_free(&conf);
+	} else {
+		fconf->id   = ot_flt_id;
+		fconf->ops  = &flt_ot_ops;
+		fconf->conf = conf;
+
+		*cur_arg = pos;
+
+		FLT_OT_DBG(3, "filter set: id '%s', config '%s'", conf->id, conf->cfg_file);
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/* Declare the filter parser for FLT_OT_OPT_NAME keyword. */
+static struct flt_kw_list flt_kws = { FLT_OT_SCOPE, { }, {
+		{ FLT_OT_OPT_NAME, flt_ot_parse, NULL },
+		{ NULL, NULL, NULL },
+	}
+};
+
+INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/pool.c b/addons/ot/src/pool.c
new file mode 100644
index 0000000..ead53e1
--- /dev/null
+++ b/addons/ot/src/pool.c
@@ -0,0 +1,223 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+/***
+ * NAME
+ *   flt_ot_pool_alloc -
+ *
+ * ARGUMENTS
+ *   pool       -
+ *   size       -
+ *   flag_clear -
+ *   err        -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+void *flt_ot_pool_alloc(struct pool_head *pool, size_t size, bool flag_clear, char **err)
+{
+	void *retptr;
+
+	FLT_OT_FUNC("%p, %zu, %hhu, %p:%p", pool, size, flag_clear, FLT_OT_DPTR_ARGS(err));
+
+	if (pool != NULL) {
+		retptr = pool_alloc(pool);
+		if (retptr != NULL)
+			FLT_OT_DBG(2, "POOL_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OT_DEREF(pool, size, size));
+	} else {
+		retptr = FLT_OT_MALLOC(size);
+	}
+
+	if (retptr == NULL)
+		FLT_OT_ERR("out of memory");
+	else if (flag_clear)
+		(void)memset(retptr, 0, size);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_pool_strndup -
+ *
+ * ARGUMENTS
+ *   pool -
+ *   s    -
+ *   size -
+ *   err  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+void *flt_ot_pool_strndup(struct pool_head *pool, const char *s, size_t size, char **err)
+{
+	void *retptr;
+
+	FLT_OT_FUNC("%p, \"%.*s\", %zu, %p:%p", pool, (int)size, s, size, FLT_OT_DPTR_ARGS(err));
+
+	if (pool != NULL) {
+		retptr = pool_alloc(pool);
+		if (retptr != NULL) {
+			(void)memcpy(retptr, s, MIN(pool->size - 1, size));
+
+			((uint8_t *)retptr)[MIN(pool->size - 1, size)] = '\0';
+		}
+	} else {
+		retptr = FLT_OT_STRNDUP(s, size);
+	}
+
+	if (retptr != NULL)
+		FLT_OT_DBG(2, "POOL_STRNDUP: %s:%d(%p %zu)", __func__, __LINE__, retptr, FLT_OT_DEREF(pool, size, size));
+	else
+		FLT_OT_ERR("out of memory");
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_pool_free -
+ *
+ * ARGUMENTS
+ *   pool -
+ *   ptr  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_pool_free(struct pool_head *pool, void **ptr)
+{
+	FLT_OT_FUNC("%p, %p:%p", pool, FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG(2, "POOL_FREE: %s:%d(%p %u)", __func__, __LINE__, *ptr, FLT_OT_DEREF(pool, size, 0));
+
+	if (pool != NULL)
+		pool_free(pool, *ptr);
+	else
+		FLT_OT_FREE(*ptr);
+
+	*ptr = NULL;
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_trash_alloc -
+ *
+ * ARGUMENTS
+ *   flag_clear -
+ *   err        -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+struct buffer *flt_ot_trash_alloc(bool flag_clear, char **err)
+{
+	struct buffer *retptr;
+
+	FLT_OT_FUNC("%hhu, %p:%p", flag_clear, FLT_OT_DPTR_ARGS(err));
+
+#ifdef USE_TRASH_CHUNK
+	retptr = alloc_trash_chunk();
+	if (retptr != NULL)
+		FLT_OT_DBG(2, "TRASH_ALLOC: %s:%d(%p %zu)", __func__, __LINE__, retptr, retptr->size);
+#else
+	retptr = FLT_OT_MALLOC(sizeof(*retptr));
+	if (retptr != NULL) {
+		chunk_init(retptr, FLT_OT_MALLOC(global.tune.bufsize), global.tune.bufsize);
+		if (retptr->area == NULL)
+			FLT_OT_FREE_CLEAR(retptr);
+		else
+			*(retptr->area) = '\0';
+	}
+#endif
+
+	if (retptr == NULL)
+		FLT_OT_ERR("out of memory");
+	else if (flag_clear)
+		(void)memset(retptr->area, 0, retptr->size);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_trash_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_trash_free(struct buffer **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG(2, "TRASH_FREE: %s:%d(%p %zu)", __func__, __LINE__, *ptr, (*ptr)->size);
+
+#ifdef USE_TRASH_CHUNK
+	free_trash_chunk(*ptr);
+#else
+	FLT_OT_FREE((*ptr)->area);
+	FLT_OT_FREE(*ptr);
+#endif
+
+	*ptr = NULL;
+
+	FLT_OT_RETURN();
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/scope.c b/addons/ot/src/scope.c
new file mode 100644
index 0000000..462c865
--- /dev/null
+++ b/addons/ot/src/scope.c
@@ -0,0 +1,631 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+static struct pool_head *pool_head_ot_scope_span = NULL;
+static struct pool_head *pool_head_ot_scope_context = NULL;
+static struct pool_head *pool_head_ot_runtime_context = NULL;
+
+#ifdef USE_POOL_OT_SCOPE_SPAN
+REGISTER_POOL(&pool_head_ot_scope_span, "ot_scope_span", sizeof(struct flt_ot_scope_span));
+#endif
+#ifdef USE_POOL_OT_SCOPE_CONTEXT
+REGISTER_POOL(&pool_head_ot_scope_context, "ot_scope_context", sizeof(struct flt_ot_scope_context));
+#endif
+#ifdef USE_POOL_OT_RUNTIME_CONTEXT
+REGISTER_POOL(&pool_head_ot_runtime_context, "ot_runtime_context", sizeof(struct flt_ot_runtime_context));
+#endif
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_pools_info -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_pools_info(void)
+{
+	/*
+	 * In case we have some error in the configuration file,
+	 * it is possible that this pool was not initialized.
+	 */
+#ifdef USE_POOL_BUFFER
+	FLT_OT_DBG(2, "sizeof_pool(buffer) = %u", FLT_OT_DEREF(pool_head_buffer, size, 0));
+#endif
+#ifdef USE_TRASH_CHUNK
+	FLT_OT_DBG(2, "sizeof_pool(trash) = %u", FLT_OT_DEREF(pool_head_trash, size, 0));
+#endif
+
+#ifdef USE_POOL_OT_SCOPE_SPAN
+	FLT_OT_DBG(2, "sizeof_pool(ot_scope_span) = %u", pool_head_ot_scope_span->size);
+#endif
+#ifdef USE_POOL_OT_SCOPE_CONTEXT
+	FLT_OT_DBG(2, "sizeof_pool(ot_scope_context) = %u", pool_head_ot_scope_context->size);
+#endif
+#ifdef USE_POOL_OT_RUNTIME_CONTEXT
+	FLT_OT_DBG(2, "sizeof_pool(ot_runtime_context) = %u", pool_head_ot_runtime_context->size);
+#endif
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_runtime_context_init -
+ *
+ * ARGUMENTS
+ *   s   -
+ *   f   -
+ *   err -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_runtime_context *flt_ot_runtime_context_init(struct stream *s, struct filter *f, char **err)
+{
+	const struct flt_ot_conf      *conf = FLT_OT_CONF(f);
+	struct flt_ot_runtime_context *retptr = NULL;
+
+	FLT_OT_FUNC("%p, %p, %p:%p", s, f, FLT_OT_DPTR_ARGS(err));
+
+	retptr = flt_ot_pool_alloc(pool_head_ot_runtime_context, sizeof(*retptr), 1, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr->stream        = s;
+	retptr->filter        = f;
+	retptr->uuid.u64[0]   = ha_random64();
+	retptr->uuid.u64[1]   = ha_random64();
+	retptr->flag_harderr  = conf->tracer->flag_harderr;
+	retptr->flag_disabled = conf->tracer->flag_disabled;
+	retptr->logging       = conf->tracer->logging;
+	LIST_INIT(&(retptr->spans));
+	LIST_INIT(&(retptr->contexts));
+
+	(void)snprintf(retptr->uuid.s, sizeof(retptr->uuid.s), "%08x-%04hx-%04hx-%04hx-%012" PRIx64,
+	               retptr->uuid.time_low,
+	               retptr->uuid.time_mid,
+	               (retptr->uuid.time_hi_and_version & UINT16_C(0xfff)) | UINT16_C(0x4000),
+	               retptr->uuid.clock_seq | UINT16_C(0x8000),
+	               (uint64_t)retptr->uuid.node);
+
+	if (flt_ot_var_register(FTL_OT_VAR_UUID, err) != -1)
+		(void)flt_ot_var_set(s, FTL_OT_VAR_UUID, retptr->uuid.s, SMP_OPT_DIR_REQ, err);
+
+	FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_runtime_context_free -
+ *
+ * ARGUMENTS
+ *   f -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_runtime_context_free(struct filter *f)
+{
+	struct flt_ot_runtime_context *rt_ctx = f->ctx;
+
+	FLT_OT_FUNC("%p", f);
+
+	if (rt_ctx == NULL)
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx);
+
+	if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
+		struct timespec           ts;
+		struct flt_ot_scope_span *span, *span_back;
+
+		/* All spans should be completed at the same time. */
+		(void)clock_gettime(CLOCK_MONOTONIC, &ts);
+
+		list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list) {
+			ot_span_finish(&(span->span), &ts, NULL, NULL, NULL);
+			flt_ot_scope_span_free(&span);
+		}
+	}
+
+	if (!LIST_ISEMPTY(&(rt_ctx->contexts))) {
+		struct flt_ot_scope_context *ctx, *ctx_back;
+
+		list_for_each_entry_safe(ctx, ctx_back, &(rt_ctx->contexts), list)
+			flt_ot_scope_context_free(&ctx);
+	}
+
+	flt_ot_pool_free(pool_head_ot_runtime_context, &(f->ctx));
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_span_init -
+ *
+ * ARGUMENTS
+ *   rt_ctx     -
+ *   id         -
+ *   id_len     -
+ *   ref_type   -
+ *   ref_id     -
+ *   ref_id_len -
+ *   dir        -
+ *   err        -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_scope_span *flt_ot_scope_span_init(struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len, otc_span_reference_type_t ref_type, const char *ref_id, size_t ref_id_len, uint dir, char **err)
+{
+	struct otc_span             *ref_span = NULL;
+	struct otc_span_context     *ref_ctx = NULL;
+	struct flt_ot_scope_span    *span, *retptr = NULL;
+	struct flt_ot_scope_context *ctx;
+
+	FLT_OT_FUNC("%p, \"%s\", %zu, %d, \"%s\", %zu, %u, %p:%p", rt_ctx, id, id_len, ref_type, ref_id, ref_id_len, dir, FLT_OT_DPTR_ARGS(err));
+
+	if ((rt_ctx == NULL) || (id == NULL))
+		FLT_OT_RETURN(retptr);
+
+	list_for_each_entry(span, &(rt_ctx->spans), list)
+		if ((span->id_len == id_len) && (memcmp(span->id, id, id_len) == 0)) {
+			FLT_OT_DBG(2, "found span %p", span);
+
+			FLT_OT_RETURN(span);
+		}
+
+	if (ref_id != NULL) {
+		list_for_each_entry(span, &(rt_ctx->spans), list)
+			if ((span->id_len == ref_id_len) && (memcmp(span->id, ref_id, ref_id_len) == 0)) {
+				ref_span = span->span;
+
+				break;
+			}
+
+		if (ref_span != NULL) {
+			FLT_OT_DBG(2, "found referenced span %p", span);
+		} else {
+			list_for_each_entry(ctx, &(rt_ctx->contexts), list)
+				if ((ctx->id_len == ref_id_len) && (memcmp(ctx->id, ref_id, ref_id_len) == 0)) {
+					ref_ctx = ctx->context;
+
+					break;
+				}
+
+			if (ref_ctx != NULL) {
+				FLT_OT_DBG(2, "found referenced context %p", ctx);
+			} else {
+				FLT_OT_ERR("cannot find referenced span/context '%s'", ref_id);
+
+				FLT_OT_RETURN(retptr);
+			}
+		}
+	}
+
+	retptr = flt_ot_pool_alloc(pool_head_ot_scope_span, sizeof(*retptr), 1, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	retptr->id          = id;
+	retptr->id_len      = id_len;
+	retptr->smp_opt_dir = dir;
+	retptr->ref_type    = ref_type;
+	retptr->ref_span    = ref_span;
+	retptr->ref_ctx     = ref_ctx;
+	LIST_ADD(&(rt_ctx->spans), &(retptr->list));
+
+	FLT_OT_DBG_SCOPE_SPAN("new span ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_span_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_scope_span_free(struct flt_ot_scope_span **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_SCOPE_SPAN("", *ptr);
+
+	/* If the span is still active, do nothing. */
+	if ((*ptr)->span != NULL) {
+		FLT_OT_DBG(2, "cannot finish active span");
+
+		FLT_OT_RETURN();
+	}
+
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	flt_ot_pool_free(pool_head_ot_scope_span, (void **)ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_context_init -
+ *
+ * ARGUMENTS
+ *   rt_ctx   -
+ *   tracer   -
+ *   id       -
+ *   id_len   -
+ *   text_map -
+ *   dir      -
+ *   err      -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct flt_ot_scope_context *flt_ot_scope_context_init(struct flt_ot_runtime_context *rt_ctx, struct otc_tracer *tracer, const char *id, size_t id_len, const struct otc_text_map *text_map, uint dir, char **err)
+{
+	struct otc_http_headers_reader  reader;
+	struct otc_span_context        *span_ctx;
+	struct flt_ot_scope_context    *retptr = NULL;
+
+	FLT_OT_FUNC("%p, %p, \"%s\", %zu, %p, %u, %p:%p", rt_ctx, tracer, id, id_len, text_map, dir, FLT_OT_DPTR_ARGS(err));
+
+	if ((rt_ctx == NULL) || (tracer == NULL) || (id == NULL) || (text_map == NULL))
+		FLT_OT_RETURN(retptr);
+
+	list_for_each_entry(retptr, &(rt_ctx->contexts), list)
+		if ((retptr->id_len == id_len) && (memcmp(retptr->id, id, id_len) == 0)) {
+			FLT_OT_DBG(2, "found context %p", retptr);
+
+			FLT_OT_RETURN(retptr);
+		}
+
+	retptr = flt_ot_pool_alloc(pool_head_ot_scope_context, sizeof(*retptr), 1, err);
+	if (retptr == NULL)
+		FLT_OT_RETURN(retptr);
+
+	span_ctx = ot_extract_http_headers(tracer, &reader, text_map, err);
+	if (span_ctx == NULL) {
+		flt_ot_scope_context_free(&retptr);
+
+		FLT_OT_RETURN(retptr);
+	}
+
+	retptr->id          = id;
+	retptr->id_len      = id_len;
+	retptr->smp_opt_dir = dir;
+	retptr->context     = span_ctx;
+	LIST_ADD(&(rt_ctx->contexts), &(retptr->list));
+
+	FLT_OT_DBG_SCOPE_CONTEXT("new context ", retptr);
+
+	FLT_OT_RETURN(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_context_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_scope_context_free(struct flt_ot_scope_context **ptr)
+{
+	FLT_OT_FUNC("%p:%p", FLT_OT_DPTR_ARGS(ptr));
+
+	if ((ptr == NULL) || (*ptr == NULL))
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_SCOPE_CONTEXT("", *ptr);
+
+	if ((*ptr)->context != NULL)
+		(*ptr)->context->destroy(&((*ptr)->context));
+
+	FLT_OT_LIST_DEL(&((*ptr)->list));
+	flt_ot_pool_free(pool_head_ot_scope_context, (void **)ptr);
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_data_free -
+ *
+ * ARGUMENTS
+ *   ptr -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_scope_data_free(struct flt_ot_scope_data *ptr)
+{
+	int i;
+
+	FLT_OT_FUNC("%p", ptr);
+
+	if (ptr == NULL)
+		FLT_OT_RETURN();
+
+	FLT_OT_DBG_SCOPE_DATA("", ptr);
+
+	for (i = 0; i < ptr->num_tags; i++)
+		if (ptr->tags[i].value.type == otc_value_string)
+			FLT_OT_FREE_VOID(ptr->tags[i].value.value.string_value);
+	otc_text_map_destroy(&(ptr->baggage), OTC_TEXT_MAP_FREE_VALUE);
+	for (i = 0; i < ptr->num_log_fields; i++)
+		if (ptr->log_fields[i].value.type == otc_value_string)
+			FLT_OT_FREE_VOID(ptr->log_fields[i].value.value.string_value);
+
+	(void)memset(ptr, 0, sizeof(*ptr));
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_finish_mark -
+ *
+ * ARGUMENTS
+ *   rt_ctx -
+ *   id     -
+ *   id_len -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_scope_finish_mark(const struct flt_ot_runtime_context *rt_ctx, const char *id, size_t id_len)
+{
+	struct flt_ot_scope_span    *span;
+	struct flt_ot_scope_context *ctx;
+	int                          span_cnt = 0, ctx_cnt = 0, retval;
+
+	FLT_OT_FUNC("%p, \"%s\", %zu", rt_ctx, id, id_len);
+
+	if (FLT_OT_STR_CMP(FLT_OT_SCOPE_SPAN_FINISH_ALL, id, id_len)) {
+		list_for_each_entry(span, &(rt_ctx->spans), list) {
+			span->flag_finish = 1;
+			span_cnt++;
+		}
+
+		list_for_each_entry(ctx, &(rt_ctx->contexts), list) {
+			ctx->flag_finish = 1;
+			ctx_cnt++;
+		}
+
+		FLT_OT_DBG(2, "marked %d span(s), %d context(s)", span_cnt, ctx_cnt);
+	}
+	else if (FLT_OT_STR_CMP(FLT_OT_SCOPE_SPAN_FINISH_REQ, id, id_len)) {
+		list_for_each_entry(span, &(rt_ctx->spans), list)
+			if (span->smp_opt_dir == SMP_OPT_DIR_REQ) {
+				span->flag_finish = 1;
+				span_cnt++;
+			}
+
+		list_for_each_entry(ctx, &(rt_ctx->contexts), list)
+			if (ctx->smp_opt_dir == SMP_OPT_DIR_REQ) {
+				ctx->flag_finish = 1;
+				span_cnt++;
+			}
+
+		FLT_OT_DBG(2, "marked REQuest channel %d span(s), %d context(s)", span_cnt, ctx_cnt);
+	}
+	else if (FLT_OT_STR_CMP(FLT_OT_SCOPE_SPAN_FINISH_RES, id, id_len)) {
+		list_for_each_entry(span, &(rt_ctx->spans), list)
+			if (span->smp_opt_dir == SMP_OPT_DIR_RES) {
+				span->flag_finish = 1;
+				span_cnt++;
+			}
+
+		list_for_each_entry(ctx, &(rt_ctx->contexts), list)
+			if (ctx->smp_opt_dir == SMP_OPT_DIR_RES) {
+				ctx->flag_finish = 1;
+				ctx_cnt++;
+			}
+
+		FLT_OT_DBG(2, "marked RESponse channel %d span(s), %d context(s)", span_cnt, ctx_cnt);
+	}
+	else {
+		list_for_each_entry(span, &(rt_ctx->spans), list)
+			if ((span->id_len == id_len) && (memcmp(span->id, id, id_len) == 0)) {
+				span->flag_finish = 1;
+				span_cnt++;
+
+				break;
+			}
+
+		list_for_each_entry(ctx, &(rt_ctx->contexts), list)
+			if ((ctx->id_len == id_len) && (memcmp(ctx->id, id, id_len) == 0)) {
+				ctx->flag_finish = 1;
+				ctx_cnt++;
+
+				break;
+			}
+
+		if (span_cnt > 0)
+			FLT_OT_DBG(2, "marked span '%s'", id);
+		if (ctx_cnt > 0)
+			FLT_OT_DBG(2, "marked context '%s'", id);
+		if ((span_cnt + ctx_cnt) == 0)
+			FLT_OT_DBG(2, "cannot find span/context '%s'", id);
+	}
+
+	retval = span_cnt + ctx_cnt;
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_finish_marked -
+ *
+ * ARGUMENTS
+ *   rt_ctx    -
+ *   ts_finish -
+ *
+ * DESCRIPTION
+ *   Finish marked spans.
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_scope_finish_marked(const struct flt_ot_runtime_context *rt_ctx, const struct timespec *ts_finish)
+{
+	struct flt_ot_scope_span    *span;
+	struct flt_ot_scope_context *ctx;
+
+	FLT_OT_FUNC("%p, %p", rt_ctx, ts_finish);
+
+	list_for_each_entry(span, &(rt_ctx->spans), list)
+		if (span->flag_finish) {
+			FLT_OT_DBG_SCOPE_SPAN("finishing span ", span);
+
+			ot_span_finish(&(span->span), ts_finish, NULL, NULL, NULL);
+
+			span->flag_finish = 0;
+		}
+
+	list_for_each_entry(ctx, &(rt_ctx->contexts), list)
+		if (ctx->flag_finish) {
+			FLT_OT_DBG_SCOPE_CONTEXT("finishing context ", ctx);
+
+			if (ctx->context != NULL)
+				ctx->context->destroy(&(ctx->context));
+
+			ctx->flag_finish = 0;
+		}
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_scope_free_unused -
+ *
+ * ARGUMENTS
+ *   rt_ctx -
+ *   chn    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_scope_free_unused(struct flt_ot_runtime_context *rt_ctx, struct channel *chn)
+{
+	FLT_OT_FUNC("%p", rt_ctx);
+
+	if (rt_ctx == NULL)
+		FLT_OT_RETURN();
+
+	if (!LIST_ISEMPTY(&(rt_ctx->spans))) {
+		struct flt_ot_scope_span *span, *span_back;
+
+		list_for_each_entry_safe(span, span_back, &(rt_ctx->spans), list)
+			if (span->span == NULL)
+				flt_ot_scope_span_free(&span);
+	}
+
+	if (!LIST_ISEMPTY(&(rt_ctx->contexts))) {
+		struct flt_ot_scope_context *ctx, *ctx_back;
+
+		list_for_each_entry_safe(ctx, ctx_back, &(rt_ctx->contexts), list)
+			if (ctx->context == NULL) {
+				/*
+				 * All headers and variables associated with
+				 * the context in question should be deleted.
+				 */
+				(void)flt_ot_http_headers_remove(chn, ctx->id, NULL);
+				(void)flt_ot_vars_unset(rt_ctx->stream, FLT_OT_VARS_SCOPE, ctx->id, ctx->smp_opt_dir, NULL);
+
+				flt_ot_scope_context_free(&ctx);
+			}
+	}
+
+	FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", rt_ctx);
+
+	FLT_OT_RETURN();
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/util.c b/addons/ot/src/util.c
new file mode 100644
index 0000000..3adc5a3
--- /dev/null
+++ b/addons/ot/src/util.c
@@ -0,0 +1,793 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_args_dump -
+ *
+ * ARGUMENTS
+ *   args -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_args_dump(char **args)
+{
+	int i, n;
+
+	for (n = 1; FLT_OT_ARG_ISVALID(n); n++);
+
+	(void)fprintf(stderr, FLT_OT_DBG_FMT("%.*sargs[%d]: { '%s' "), dbg_indent_level, FLT_OT_DBG_INDENT, n, args[0]);
+
+	for (i = 1; FLT_OT_ARG_ISVALID(i); i++)
+		(void)fprintf(stderr, "'%s' ", args[i]);
+
+	(void)fprintf(stderr, "}\n");
+}
+
+
+/***
+ * NAME
+ *   flt_ot_filters_dump -
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_filters_dump(void)
+{
+	struct flt_conf *fconf;
+	struct proxy    *px;
+
+	FLT_OT_FUNC("");
+
+	for (px = proxies_list; px != NULL; px = px->next) {
+		FLT_OT_DBG(2, "proxy '%s'", px->id);
+
+		list_for_each_entry(fconf, &(px->filter_configs), list)
+			if (fconf->id == ot_flt_id) {
+				struct flt_ot_conf *conf = fconf->conf;
+
+				FLT_OT_DBG(2, "  OT filter '%s'", conf->id);
+			}
+	}
+
+	FLT_OT_RETURN();
+}
+
+
+/***
+ * NAME
+ *   flt_ot_chn_label -
+ *
+ * ARGUMENTS
+ *   chn -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_chn_label(const struct channel *chn)
+{
+	return (chn->flags & CF_ISRESP) ? "RESponse" : "REQuest";
+}
+
+
+/***
+ * NAME
+ *   flt_ot_pr_mode -
+ *
+ * ARGUMENTS
+ *   s -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_pr_mode(const struct stream *s)
+{
+	struct proxy *px = (s->flags & SF_BE_ASSIGNED) ? s->be : strm_fe(s);
+
+	return (px->mode == PR_MODE_HTTP) ? "HTTP" : "TCP";
+}
+
+
+/***
+ * NAME
+ *   flt_ot_stream_pos -
+ *
+ * ARGUMENTS
+ *   s -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_stream_pos(const struct stream *s)
+{
+	return (s->flags & SF_BE_ASSIGNED) ? "backend" : "frontend";
+}
+
+
+/***
+ * NAME
+ *   flt_ot_type -
+ *
+ * ARGUMENTS
+ *   f -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_type(const struct filter *f)
+{
+	return (f->flags & FLT_FL_IS_BACKEND_FILTER) ? "backend" : "frontend";
+}
+
+
+/***
+ * NAME
+ *   flt_ot_analyzer -
+ *
+ * ARGUMENTS
+ *   an_bit -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_analyzer(uint an_bit)
+{
+#define FLT_OT_AN_DEF(a)   { a, #a },
+	static const struct {
+		uint        an_bit;
+		const char *str;
+	} flt_ot_an[] = { FLT_OT_AN_DEFINES };
+#undef FLT_OT_AN_DEF
+	const char *retptr = "invalid an_bit";
+	int         i;
+
+	for (i = 0; i < FLT_OT_TABLESIZE(flt_ot_an); i++)
+		if (flt_ot_an[i].an_bit == an_bit) {
+			retptr = flt_ot_an[i].str;
+
+			break;
+		}
+
+	return retptr;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_str_hex -
+ *
+ * ARGUMENTS
+ *   data -
+ *   size -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_str_hex(const void *data, size_t size)
+{
+	static THREAD_LOCAL char  retbuf[BUFSIZ];
+	const uint8_t            *ptr = data;
+	size_t                    i;
+
+	if (data == NULL)
+		return "(null)";
+	else if (size == 0)
+		return "()";
+
+	for (i = 0, size <<= 1; (i < (sizeof(retbuf) - 2)) && (i < size); ptr++) {
+		retbuf[i++] = FLT_OT_NIBBLE_TO_HEX(*ptr >> 4);
+		retbuf[i++] = FLT_OT_NIBBLE_TO_HEX(*ptr & 0x0f);
+	}
+
+	retbuf[i] = '\0';
+
+	return retbuf;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_str_ctrl -
+ *
+ * ARGUMENTS
+ *   data -
+ *   size -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_str_ctrl(const void *data, size_t size)
+{
+	static THREAD_LOCAL char  retbuf[BUFSIZ];
+	const uint8_t            *ptr = data;
+	size_t                    i, n = 0;
+
+	if (data == NULL)
+		return "(null)";
+	else if (size == 0)
+		return "()";
+
+	for (i = 0; (n < (sizeof(retbuf) - 1)) && (i < size); i++)
+		retbuf[n++] = ((ptr[i] >= 0x20) && (ptr[i] <= 0x7e)) ? ptr[i] : '.';
+
+	retbuf[n] = '\0';
+
+	return retbuf;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_list_debug -
+ *
+ * ARGUMENTS
+ *   head -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+const char *flt_ot_list_debug(const struct list *head)
+{
+	FLT_OT_BUFFER_THR(retbuf, 4, 64, retptr);
+
+	if ((head == NULL) || LIST_ISEMPTY(head)) {
+		(void)strncpy(retptr, (head == NULL) ? "{ null list }" : "{ empty list }", sizeof(retbuf[0]));
+	}
+	else if (head->p == head->n) {
+		(void)snprintf(retptr, sizeof(retbuf[0]), "{ %p * 1 }", head->p);
+	}
+	else {
+		const struct list *ptr;
+		size_t             count = 0;
+
+		for (ptr = head->n; ptr != head; ptr = ptr->n, count++);
+
+		(void)snprintf(retptr, sizeof(retbuf[0]), "{ %p %p %zu }", head->p, head->n, count);
+	}
+
+	return (retptr);
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_chunk_add -
+ *
+ * ARGUMENTS
+ *   chk -
+ *   src -
+ *   n   -
+ *   err -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+ssize_t flt_ot_chunk_add(struct buffer *chk, const void *src, size_t n, char **err)
+{
+	FLT_OT_FUNC("%p, %p, %zu, %p:%p", chk, src, n, FLT_OT_DPTR_ARGS(err));
+
+	if ((chk == NULL) || (src == NULL))
+		FLT_OT_RETURN(-1);
+
+	if (chk->area == NULL)
+		chunk_init(chk, FLT_OT_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
+
+	if (chk->area == NULL) {
+		FLT_OT_ERR("out of memory");
+
+		FLT_OT_RETURN(-1);
+	}
+	else if (n > (chk->size - chk->data)) {
+		FLT_OT_ERR("chunk size too small");
+
+		FLT_OT_RETURN(-1);
+	}
+
+	(void)memcpy(chk->area + chk->data, src, n);
+	chk->data += n;
+
+	FLT_OT_RETURN(chk->data);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_args_count -
+ *
+ * ARGUMENTS
+ *   args -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_args_count(char **args)
+{
+	int retval = 0;
+
+	if (args != NULL)
+		for ( ; FLT_OT_ARG_ISVALID(retval); retval++);
+
+	return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_args_to_str -
+ *
+ * ARGUMENTS
+ *   args -
+ *   idx  -
+ *   str  -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_args_to_str(char **args, int idx, char **str)
+{
+	int i;
+
+	if ((args == NULL) || (*args == NULL))
+		return;
+
+	for (i = idx; FLT_OT_ARG_ISVALID(i); i++)
+		(void)memprintf(str, "%s%s%s", (*str == NULL) ? "" : *str, (i == idx) ? "" : " ", args[i]);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_strtod -
+ *
+ * ARGUMENTS
+ *   nptr      -
+ *   limit_min -
+ *   limit_max -
+ *   err       -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+double flt_ot_strtod(const char *nptr, double limit_min, double limit_max, char **err)
+{
+	char   *endptr = NULL;
+	double  retval;
+
+	errno = 0;
+
+	retval = strtod(nptr, &endptr);
+	if ((errno != 0) || FLT_OT_STR_ISVALID(endptr))
+		FLT_OT_ERR("'%s' : invalid value", nptr);
+	else if (!FLT_OT_IN_RANGE(retval, limit_min, limit_max))
+		FLT_OT_ERR("'%s' : value out of range [%.2f, %.2f]", nptr, limit_min, limit_max);
+
+	return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_strtoll -
+ *
+ * ARGUMENTS
+ *   nptr      -
+ *   limit_min -
+ *   limit_max -
+ *   err       -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int64_t flt_ot_strtoll(const char *nptr, int64_t limit_min, int64_t limit_max, char **err)
+{
+	char    *endptr = NULL;
+	int64_t  retval;
+
+	errno = 0;
+
+	retval = strtoll(nptr, &endptr, 0);
+	if ((errno != 0) || FLT_OT_STR_ISVALID(endptr))
+		FLT_OT_ERR("'%s' : invalid value", nptr);
+	else if (!FLT_OT_IN_RANGE(retval, limit_min, limit_max))
+		FLT_OT_ERR("'%s' : value out of range [%" PRId64 ", %" PRId64 "]", nptr, limit_min, limit_max);
+
+	return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_sample_to_str -
+ *
+ * ARGUMENTS
+ *   data  -
+ *   value -
+ *   size  -
+ *   err   -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err)
+{
+	int retval = -1;
+
+	FLT_OT_FUNC("%p, %p, %zu, %p:%p", data, value, size, FLT_OT_DPTR_ARGS(err));
+
+	if ((data == NULL) || (value == NULL) || (size == 0))
+		FLT_OT_RETURN(retval);
+
+	*value = '\0';
+
+	if (data->type == SMP_T_ANY) {
+		FLT_OT_ERR("invalid sample data type %d", data->type);
+	}
+	else if (data->type == SMP_T_BOOL) {
+		value[0] = data->u.sint ? '1' : '0';
+		value[1] = '\0';
+
+		retval = 1;
+	}
+	else if (data->type == SMP_T_SINT) {
+		retval = snprintf(value, size, "%lld", data->u.sint);
+	}
+	else if (data->type == SMP_T_ADDR) {
+		/* This type is never used to qualify a sample. */
+	}
+	else if (data->type == SMP_T_IPV4) {
+		if (INET_ADDRSTRLEN > size)
+			FLT_OT_ERR("sample data size too large");
+		else if (inet_ntop(AF_INET, &(data->u.ipv4), value, INET_ADDRSTRLEN) == NULL)
+			FLT_OT_ERR("invalid IPv4 address");
+		else
+			retval = strlen(value);
+	}
+	else if (data->type == SMP_T_IPV6) {
+		if (INET6_ADDRSTRLEN > size)
+			FLT_OT_ERR("sample data size too large");
+		else if (inet_ntop(AF_INET6, &(data->u.ipv6), value, INET6_ADDRSTRLEN) == NULL)
+			FLT_OT_ERR("invalid IPv6 address");
+		else
+			retval = strlen(value);
+	}
+	else if (data->type == SMP_T_STR) {
+		if (data->u.str.data >= size) {
+			FLT_OT_ERR("sample data size too large");
+		}
+		else if (data->u.str.data > 0) {
+			retval = data->u.str.data;
+
+			(void)strncat(value, data->u.str.area, retval);
+		}
+		else {
+			/*
+			 * There is no content to add but we will still return
+			 * the correct status.
+			 */
+			retval = 0;
+		}
+	}
+	else if (data->type == SMP_T_BIN) {
+		FLT_OT_ERR("invalid sample data type %d", data->type);
+	}
+	else if (data->type != SMP_T_METH) {
+		FLT_OT_ERR("invalid sample data type %d", data->type);
+	}
+	else if (data->u.meth.meth == HTTP_METH_OPTIONS) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_OPTIONS);
+
+		(void)memcpy(value, HTTP_METH_STR_OPTIONS, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_GET) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_GET);
+
+		(void)memcpy(value, HTTP_METH_STR_GET, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_HEAD) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_HEAD);
+
+		(void)memcpy(value, HTTP_METH_STR_HEAD, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_POST) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_POST);
+
+		(void)memcpy(value, HTTP_METH_STR_POST, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_PUT) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_PUT);
+
+		(void)memcpy(value, HTTP_METH_STR_PUT, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_DELETE) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_DELETE);
+
+		(void)memcpy(value, HTTP_METH_STR_DELETE, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_TRACE) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_TRACE);
+
+		(void)memcpy(value, HTTP_METH_STR_TRACE, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_CONNECT) {
+		retval = FLT_OT_STR_SIZE(HTTP_METH_STR_CONNECT);
+
+		(void)memcpy(value, HTTP_METH_STR_CONNECT, retval + 1);
+	}
+	else if (data->u.meth.meth == HTTP_METH_OTHER) {
+		if (data->u.meth.str.data >= size) {
+			FLT_OT_ERR("sample data size too large");
+		} else {
+			retval = data->u.meth.str.data;
+
+			(void)strncat(value, data->u.meth.str.area, retval);
+		}
+	}
+	else {
+		FLT_OT_ERR("invalid HTTP method");
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_sample_to_value -
+ *
+ * ARGUMENTS
+ *   key   -
+ *   data  -
+ *   value -
+ *   err   -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_sample_to_value(const char *key, const struct sample_data *data, struct otc_value *value, char **err)
+{
+	int retval = -1;
+
+	FLT_OT_FUNC("\"%s\", %p, %p, %p:%p", key, data, value, FLT_OT_DPTR_ARGS(err));
+
+	if ((data == NULL) || (value == NULL))
+		FLT_OT_RETURN(retval);
+
+	if (data->type == SMP_T_BOOL) {
+		value->type             = otc_value_bool;
+		value->value.bool_value = data->u.sint ? 1 : 0;
+
+		retval = sizeof(value->value.bool_value);
+	}
+	else if (data->type == SMP_T_SINT) {
+		value->type              = otc_value_int64;
+		value->value.int64_value = data->u.sint;
+
+		retval = sizeof(value->value.int64_value);
+	}
+	else {
+		value->type               = otc_value_string;
+		value->value.string_value = FLT_OT_MALLOC(global.tune.bufsize);
+
+		if (value->value.string_value == NULL)
+			FLT_OT_ERR("out of memory");
+		else
+			retval = flt_ot_sample_to_str(data, (char *)value->value.string_value, global.tune.bufsize, err);
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_sample_add -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   dir    -
+ *   sample -
+ *   data   -
+ *   type   -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   Returns a negative value if an error occurs, 0 if it needs to wait,
+ *   any other value otherwise.
+ */
+int flt_ot_sample_add(struct stream *s, uint dir, struct flt_ot_conf_sample *sample, struct flt_ot_scope_data *data, int type, char **err)
+{
+	const struct flt_ot_conf_sample_expr *expr;
+	struct sample                         smp;
+	struct otc_value                      value;
+	struct buffer                         buffer;
+	int                                   idx = 0, rc, retval = FLT_OT_RET_OK;
+
+	FLT_OT_FUNC("%p, %u, %p, %p, %d, %p:%p", s, dir, data, sample, type, FLT_OT_DPTR_ARGS(err));
+
+	FLT_OT_DBG_CONF_SAMPLE("sample ", sample);
+
+	(void)memset(&buffer, 0, sizeof(buffer));
+
+	list_for_each_entry(expr, &(sample->exprs), list) {
+		FLT_OT_DBG_CONF_SAMPLE_EXPR("sample expression ", expr);
+
+		(void)memset(&smp, 0, sizeof(smp));
+
+		/*
+		 * If we have only one expression to process, then the data
+		 * type that is the result of the expression is converted to
+		 * an equivalent data type (if possible) that is written to
+		 * the tracer.
+		 *
+		 * If conversion is not possible, or if we have multiple
+		 * expressions to process, then the result is converted to
+		 * a string and as such sent to the tracer.
+		 */
+		if (sample_process(s->be, s->sess, s, dir | SMP_OPT_FINAL, expr->expr, &smp) != NULL) {
+			FLT_OT_DBG(3, "data type %d: '%s'", smp.data.type, expr->value);
+		} else {
+			FLT_OT_DBG(2, "WARNING: failed to fetch '%s' value", expr->value);
+
+			/*
+			 * In case the fetch failed, we will set the result
+			 * (sample) to an empty static string.
+			 */
+			(void)memset(&(smp.data), 0, sizeof(smp.data));
+			smp.data.type       = SMP_T_STR;
+			smp.data.u.str.area = "";
+		}
+
+		if ((sample->num_exprs == 1) && (type == FLT_OT_EVENT_SAMPLE_TAG)) {
+			if (flt_ot_sample_to_value(sample->key, &(smp.data), &value, err) == -1)
+				retval = FLT_OT_RET_ERROR;
+		} else {
+			if (buffer.area == NULL) {
+				chunk_init(&buffer, FLT_OT_CALLOC(1, global.tune.bufsize), global.tune.bufsize);
+				if (buffer.area == NULL) {
+					FLT_OT_ERR("out of memory");
+
+					retval = FLT_OT_RET_ERROR;
+
+					break;
+				}
+			}
+
+			rc = flt_ot_sample_to_str(&(smp.data), buffer.area + buffer.data, buffer.size - buffer.data, err);
+			if (rc == -1) {
+				retval = FLT_OT_RET_ERROR;
+			} else {
+				buffer.data += rc;
+
+				if (sample->num_exprs == ++idx) {
+					value.type               = otc_value_string;
+					value.value.string_value = buffer.area;
+				}
+			}
+		}
+	}
+
+	if (retval == FLT_OT_RET_ERROR) {
+		/* Do nothing. */
+	}
+	else if (type == FLT_OT_EVENT_SAMPLE_TAG) {
+		struct otc_tag *tag = data->tags + data->num_tags++;
+
+		tag->key = sample->key;
+		(void)memcpy(&(tag->value), &value, sizeof(tag->value));
+	}
+	else if (type == FLT_OT_EVENT_SAMPLE_LOG) {
+		struct otc_log_field *log_field = data->log_fields + data->num_log_fields++;
+
+		log_field->key = sample->key;
+		(void)memcpy(&(log_field->value), &value, sizeof(log_field->value));
+	}
+	else {
+		if (data->baggage == NULL)
+			data->baggage = otc_text_map_new(NULL, FLT_OT_MAXBAGGAGES);
+
+		if (data->baggage == NULL) {
+			FLT_OT_ERR("out of memory");
+
+			retval = FLT_OT_RET_ERROR;
+		}
+		else if (otc_text_map_add(data->baggage, sample->key, 0, value.value.string_value, 0, 0) == -1) {
+			FLT_OT_ERR("out of memory");
+
+			retval = FLT_OT_RET_ERROR;
+		}
+		else
+			FLT_OT_DBG(3, "baggage[%zu]: '%s' -> '%s'", data->baggage->count - 1, data->baggage->key[data->baggage->count - 1], data->baggage->value[data->baggage->count - 1]);
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/src/vars.c b/addons/ot/src/vars.c
new file mode 100644
index 0000000..0db5514
--- /dev/null
+++ b/addons/ot/src/vars.c
@@ -0,0 +1,578 @@
+/***
+ * Copyright 2020 HAProxy Technologies
+ *
+ * This file is part of the HAProxy OpenTracing filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "include.h"
+
+
+#ifdef DEBUG_OT
+
+/***
+ * NAME
+ *   flt_ot_vars_scope_dump -
+ *
+ * ARGUMENTS
+ *   vars  -
+ *   scope -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+static void flt_ot_vars_scope_dump(struct vars *vars, const char *scope)
+{
+	const struct var *var;
+
+	if (vars == NULL)
+		return;
+
+	HA_RWLOCK_RDLOCK(VARS_LOCK, &(vars->rwlock));
+	list_for_each_entry(var, &(vars->head), l)
+		FLT_OT_DBG(2, "'%s.%s' -> '%.*s'", scope, var->name, (int)var->data.u.str.data, var->data.u.str.area);
+	HA_RWLOCK_RDUNLOCK(VARS_LOCK, &(vars->rwlock));
+}
+
+
+/***
+ * NAME
+ *   flt_ot_vars_dump -
+ *
+ * ARGUMENTS
+ *   s -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   This function does not return a value.
+ */
+void flt_ot_vars_dump(struct stream *s)
+{
+	FLT_OT_FUNC("%p", s);
+
+	/*
+	 * It would be nice if we could use the get_vars() function from HAProxy
+	 * source here to get the value of the 'vars' pointer, but it is defined
+	 * as 'static inline', so unfortunately none of this is possible.
+	 */
+	flt_ot_vars_scope_dump(&(global.vars), "PROC");
+	flt_ot_vars_scope_dump(&(s->sess->vars), "SESS");
+	flt_ot_vars_scope_dump(&(s->vars_txn), "TXN");
+	flt_ot_vars_scope_dump(&(s->vars_reqres), "REQ/RES");
+
+	FLT_OT_RETURN();
+}
+
+#endif /* DEBUG_OT */
+
+
+/***
+ * NAME
+ *   flt_ot_get_vars -
+ *
+ * ARGUMENTS
+ *   s     -
+ *   scope -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static inline struct vars *flt_ot_get_vars(struct stream *s, const char *scope)
+{
+	struct vars *retptr = NULL;
+
+	if (strcasecmp(scope, "proc") == 0)
+		retptr = &(global.vars);
+	else if (strcasecmp(scope, "sess") == 0)
+		retptr = (&(s->sess->vars));
+	else if (strcasecmp(scope, "txn") == 0)
+		retptr = (&(s->vars_txn));
+	else if ((strcasecmp(scope, "req") == 0) || (strcasecmp(scope, "res") == 0))
+		retptr = (&(s->vars_reqres));
+
+	return retptr;
+}
+
+
+/***
+ * NAME
+ *   flt_ot_normalize_name -
+ *
+ * ARGUMENTS
+ *   var_name -
+ *   size     -
+ *   len      -
+ *   name     -
+ *   err      -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_normalize_name(char *var_name, size_t size, int *len, const char *name, char **err)
+{
+	int retval = 0;
+
+	FLT_OT_FUNC("%p, %zu, %p, \"%s\", %p:%p", var_name, size, len, name, FLT_OT_DPTR_ARGS(err));
+
+	if (!FLT_OT_STR_ISVALID(name))
+		FLT_OT_RETURN(retval);
+
+	/*
+	 * In case the name of the variable consists of several elements,
+	 * the character '.' is added between them.
+	 */
+	if ((*len == 0) || (var_name[*len - 1] == '.'))
+		/* Do nothing. */;
+	else if (*len < (size - 1))
+		var_name[(*len)++] = '.';
+	else
+		retval = -1;
+
+	/*
+	 * HAProxy does not allow the use of variable names containing '-'
+	 * or ' '.  This of course applies to HTTP header names as well.
+	 * Also, here the capital letters are converted to lowercase.
+	 */
+	while (retval != -1)
+		if (*len >= (size - 1)) {
+			FLT_OT_ERR("failed to normalize variable name, buffer too small");
+
+			retval = -1;
+		} else {
+			uint8_t ch = name[retval];
+
+			if (ch == '\0')
+				break;
+			else if (ch == '-')
+				ch = FLT_OT_VAR_CHAR_DASH;
+			else if (ch == ' ')
+				ch = FLT_OT_VAR_CHAR_SPACE;
+			else if (isupper(ch))
+				ch = ist_lc[ch];
+
+			var_name[(*len)++] = ch;
+			retval++;
+		}
+
+	var_name[*len] = '\0';
+
+	FLT_OT_DBG(3, "var_name: \"%s\" %d/%d", var_name, retval, *len);
+
+	if (retval == -1)
+		*len = retval;
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_var_name -
+ *
+ * ARGUMENTS
+ *   scope    -
+ *   prefix   -
+ *   name     -
+ *   var_name -
+ *   size     -
+ *   err      -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+static int flt_ot_var_name(const char *scope, const char *prefix, const char *name, char *var_name, size_t size, char **err)
+{
+	int retval = 0;
+
+	FLT_OT_FUNC("\"%s\", \"%s\", \"%s\", %p, %zu, %p:%p", scope, prefix, name, var_name, size, FLT_OT_DPTR_ARGS(err));
+
+	if (flt_ot_normalize_name(var_name, size, &retval, scope, err) >= 0)
+		if (flt_ot_normalize_name(var_name, size, &retval, prefix, err) >= 0)
+			(void)flt_ot_normalize_name(var_name, size, &retval, name, err);
+
+	if (retval == -1)
+		FLT_OT_ERR("failed to construct variable name '%s.%s.%s'", scope, prefix, name);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_var_register -
+ *
+ * ARGUMENTS
+ *   scope  -
+ *   prefix -
+ *   name   -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_var_register(const char *scope, const char *prefix, const char *name, char **err)
+{
+	struct arg arg;
+	char       var_name[BUFSIZ];
+	int        retval;
+
+	FLT_OT_FUNC("\"%s\", \"%s\", \"%s\", %p:%p", scope, prefix, name, FLT_OT_DPTR_ARGS(err));
+
+	retval = flt_ot_var_name(scope, prefix, name, var_name, sizeof(var_name), err);
+	if (retval == -1)
+		FLT_OT_RETURN(retval);
+
+	/* Set <size> to 0 to not release var_name memory in vars_check_arg(). */
+	(void)memset(&arg, 0, sizeof(arg));
+	arg.type          = ARGT_STR;
+	arg.data.str.area = var_name;
+	arg.data.str.data = retval;
+
+	if (vars_check_arg(&arg, err) == 0) {
+		FLT_OT_ERR_APPEND("failed to register variable '%s': %s", var_name, *err);
+
+		retval = -1;
+	} else {
+		FLT_OT_DBG(2, "variable '%s' registered", arg.data.var.name);
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_var_set -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   scope  -
+ *   prefix -
+ *   name   -
+ *   value  -
+ *   opt    -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_var_set(struct stream *s, const char *scope, const char *prefix, const char *name, const char *value, uint opt, char **err)
+{
+	struct sample smp;
+	char          var_name[BUFSIZ];
+	int           retval;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, name, value, opt, FLT_OT_DPTR_ARGS(err));
+
+	retval = flt_ot_var_name(scope, prefix, name, var_name, sizeof(var_name), err);
+	if (retval == -1)
+		FLT_OT_RETURN(retval);
+
+	(void)memset(&smp, 0, sizeof(smp));
+	(void)smp_set_owner(&smp, s->be, s->sess, s, opt | SMP_OPT_FINAL);
+	smp.data.type       = SMP_T_STR;
+	smp.data.u.str.area = (char *)value;
+	smp.data.u.str.data = strlen(value);
+
+	vars_set_by_name_ifexist(var_name, retval, &smp);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_var_unset -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   scope  -
+ *   prefix -
+ *   name   -
+ *   opt    -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_var_unset(struct stream *s, const char *scope, const char *prefix, const char *name, uint opt, char **err)
+{
+	struct sample smp;
+	char          var_name[BUFSIZ];
+	int           retval;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, name, opt, FLT_OT_DPTR_ARGS(err));
+
+	retval = flt_ot_var_name(scope, prefix, name, var_name, sizeof(var_name), err);
+	if (retval == -1)
+		FLT_OT_RETURN(retval);
+
+	(void)memset(&smp, 0, sizeof(smp));
+	(void)smp_set_owner(&smp, s->be, s->sess, s, opt | SMP_OPT_FINAL);
+
+	vars_unset_by_name_ifexist(var_name, retval, &smp);
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_vars_unset -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   scope  -
+ *   prefix -
+ *   opt    -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_vars_unset(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
+{
+	struct sample  smp;
+	struct vars   *vars;
+	struct var    *var, *var_back;
+	char           var_prefix[BUFSIZ], var_name[BUFSIZ];
+	uint           size;
+	int            var_prefix_len, var_name_len, retval = -1;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, opt, FLT_OT_DPTR_ARGS(err));
+
+	vars = flt_ot_get_vars(s, scope);
+	if (vars == NULL)
+		FLT_OT_RETURN(retval);
+
+	var_prefix_len = flt_ot_var_name(NULL, prefix, NULL, var_prefix, sizeof(var_prefix), err);
+	if (var_prefix_len == -1)
+		FLT_OT_RETURN(retval);
+
+	retval = 0;
+
+	HA_RWLOCK_WRLOCK(VARS_LOCK, &(vars->rwlock));
+	list_for_each_entry_safe(var, var_back, &(vars->head), l) {
+		FLT_OT_DBG(3, "variable cmp '%s' '%s' %d", var_prefix, var->name, var_prefix_len);
+
+		if (strncmp(var_prefix, var->name, var_prefix_len) == 0) {
+			var_name_len = snprintf(var_name, sizeof(var_name), "%s.%s", scope, var->name);
+			if ((var_name_len == -1) || (var_name_len >= sizeof(var_name))) {
+				FLT_OT_DBG(2, "'%s.%s' variable name too long", scope, var->name);
+
+				break;
+			}
+
+			FLT_OT_DBG(2, "- '%s' -> '%.*s'", var_name, (int)var->data.u.str.data, var->data.u.str.area);
+
+			(void)memset(&smp, 0, sizeof(smp));
+			(void)smp_set_owner(&smp, s->be, s->sess, s, opt | SMP_OPT_FINAL);
+
+			size = var_clear(var);
+			var_accounting_diff(vars, smp.sess, smp.strm, -size);
+
+			retval++;
+		}
+	}
+	HA_RWLOCK_WRUNLOCK(VARS_LOCK, &(vars->rwlock));
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_var_get -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   scope  -
+ *   prefix -
+ *   name   -
+ *   value  -
+ *   opt    -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+int flt_ot_var_get(struct stream *s, const char *scope, const char *prefix, const char *name, char **value, uint opt, char **err)
+{
+	struct sample smp;
+	char          var_name[BUFSIZ], var_value[BUFSIZ];
+	int           retval;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", %p:%p, %u, %p:%p", s, scope, prefix, name, FLT_OT_DPTR_ARGS(value), opt, FLT_OT_DPTR_ARGS(err));
+
+	retval = flt_ot_var_name(scope, prefix, name, var_name, sizeof(var_name), err);
+	if (retval == -1)
+		FLT_OT_RETURN(retval);
+
+	(void)memset(&smp, 0, sizeof(smp));
+	(void)smp_set_owner(&smp, s->be, s->sess, s, opt | SMP_OPT_FINAL);
+
+	if (vars_get_by_name(var_name, retval, &smp)) {
+		retval = flt_ot_sample_to_str(&(smp.data), var_value, sizeof(var_value), err);
+		if (retval != -1)
+			FLT_OT_DBG(3, "data type %d: '%s' = '%s'", smp.data.type, var_name, var_value);
+	} else {
+		FLT_OT_ERR("failed to get variable '%s'", var_name);
+
+		retval = -1;
+	}
+
+	FLT_OT_RETURN(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_ot_vars_get -
+ *
+ * ARGUMENTS
+ *   s      -
+ *   scope  -
+ *   prefix -
+ *   opt    -
+ *   err    -
+ *
+ * DESCRIPTION
+ *   -
+ *
+ * RETURN VALUE
+ *   -
+ */
+struct otc_text_map *flt_ot_vars_get(struct stream *s, const char *scope, const char *prefix, uint opt, char **err)
+{
+	struct vars         *vars;
+	const struct var    *var;
+	char                 var_name[BUFSIZ], ot_var_name[BUFSIZ];
+	int                  rc, i;
+	struct otc_text_map *retptr = NULL;
+
+	FLT_OT_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, opt, FLT_OT_DPTR_ARGS(err));
+
+	vars = flt_ot_get_vars(s, scope);
+	if (vars == NULL)
+		FLT_OT_RETURN(retptr);
+
+	rc = flt_ot_var_name(NULL, prefix, NULL, var_name, sizeof(var_name), err);
+	if (rc == -1)
+		FLT_OT_RETURN(retptr);
+
+	HA_RWLOCK_RDLOCK(VARS_LOCK, &(vars->rwlock));
+	list_for_each_entry(var, &(vars->head), l) {
+		FLT_OT_DBG(3, "variable cmp '%s' '%s' %d", var_name, var->name, rc);
+
+		if (strncmp(var_name, var->name, rc) == 0) {
+			FLT_OT_DBG(2, "'%s.%s' -> '%.*s'", scope, var->name, (int)var->data.u.str.data, var->data.u.str.area);
+
+			if (retptr == NULL) {
+				retptr = otc_text_map_new(NULL, 8);
+				if (retptr == NULL) {
+					FLT_OT_ERR("failed to create data");
+
+					break;
+				}
+			}
+
+			/*
+			 * Eh, because the use of some characters is not allowed
+			 * in the variable name, the conversion of the replaced
+			 * characters to the original is performed here.
+			 */
+			for (i = 0; ; )
+				if (i >= (FLT_OT_TABLESIZE(ot_var_name) - 1)) {
+					FLT_OT_ERR("failed to reverse variable name, buffer too small");
+
+					otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+
+					break;
+				} else {
+					char ch = var->name[rc + i + 1];
+
+					if (ch == '\0')
+						break;
+					else if (ch == FLT_OT_VAR_CHAR_DASH)
+						ch = '-';
+					else if (ch == FLT_OT_VAR_CHAR_SPACE)
+						ch = ' ';
+
+					ot_var_name[i++] = ch;
+				}
+			ot_var_name[i] = '\0';
+
+			if (retptr == NULL) {
+				break;
+			}
+			else if (otc_text_map_add(retptr, ot_var_name, i, var->data.u.str.area, var->data.u.str.data, OTC_TEXT_MAP_DUP_KEY | OTC_TEXT_MAP_DUP_VALUE) == -1) {
+				FLT_OT_ERR("failed to add map data");
+
+				otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+
+				break;
+			}
+		}
+	}
+	HA_RWLOCK_RDUNLOCK(VARS_LOCK, &(vars->rwlock));
+
+	ot_text_map_show(retptr);
+
+	if ((retptr != NULL) && (retptr->count == 0)) {
+		FLT_OT_DBG(2, "WARNING: no variables found");
+
+		otc_text_map_destroy(&retptr, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+	}
+
+	FLT_OT_RETURN(retptr);
+}
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
diff --git a/addons/ot/test/README-speed-cmp b/addons/ot/test/README-speed-cmp
new file mode 100644
index 0000000..9251faa
--- /dev/null
+++ b/addons/ot/test/README-speed-cmp
@@ -0,0 +1,111 @@
+--- rate-limit 100.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   650.95us  431.15us  46.44ms   96.67%
+    Req/Sec     1.44k    51.39     2.57k    74.89%
+  Latency Distribution
+     50%  608.00us
+     75%  760.00us
+     90%    0.91ms
+     99%    1.31ms
+  3434836 requests in 5.00m, 0.89GB read
+Requests/sec:  11446.99
+Transfer/sec:      3.03MB
+----------------------------------------------------------------------
+
+--- rate-limit 50.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   398.00us  371.39us  22.56ms   97.23%
+    Req/Sec     2.32k    84.01     2.76k    74.84%
+  Latency Distribution
+     50%  350.00us
+     75%  467.00us
+     90%  593.00us
+     99%    1.03ms
+  5530848 requests in 5.00m, 1.43GB read
+Requests/sec:  18434.31
+Transfer/sec:      4.89MB
+----------------------------------------------------------------------
+
+--- rate-limit 10.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   316.75us  351.92us  23.00ms   98.57%
+    Req/Sec     2.87k    94.02     3.22k    79.30%
+  Latency Distribution
+     50%  273.00us
+     75%  342.00us
+     90%  424.00us
+     99%    0.94ms
+  6859293 requests in 5.00m, 1.78GB read
+Requests/sec:  22862.16
+Transfer/sec:      6.06MB
+----------------------------------------------------------------------
+
+--- rate-limit 2.5 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   307.90us  368.64us  26.08ms   98.71%
+    Req/Sec     2.96k   103.84     3.23k    83.76%
+  Latency Distribution
+     50%  264.00us
+     75%  327.00us
+     90%  402.00us
+     99%    0.97ms
+  7065667 requests in 5.00m, 1.83GB read
+Requests/sec:  23550.37
+Transfer/sec:      6.24MB
+----------------------------------------------------------------------
+
+--- rate-limit 0.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   304.60us  376.36us  30.26ms   98.74%
+    Req/Sec     2.99k   106.93     3.24k    83.08%
+  Latency Distribution
+     50%  262.00us
+     75%  323.00us
+     90%  396.00us
+     99%    0.95ms
+  7136261 requests in 5.00m, 1.85GB read
+Requests/sec:  23785.77
+Transfer/sec:      6.31MB
+----------------------------------------------------------------------
+
+--- rate-limit disabled --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   300.90us  342.35us  22.13ms   98.74%
+    Req/Sec     3.00k    95.67     3.33k    81.11%
+  Latency Distribution
+     50%  261.00us
+     75%  322.00us
+     90%  394.00us
+     99%  806.00us
+  7159525 requests in 5.00m, 1.85GB read
+Requests/sec:  23863.05
+Transfer/sec:      6.33MB
+----------------------------------------------------------------------
+
+--- rate-limit off --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   302.51us  371.99us  30.26ms   98.77%
+    Req/Sec     3.00k   104.43     3.73k    83.74%
+  Latency Distribution
+     50%  260.00us
+     75%  321.00us
+     90%  394.00us
+     99%    0.89ms
+  7170345 requests in 5.00m, 1.86GB read
+Requests/sec:  23898.19
+Transfer/sec:      6.34MB
+----------------------------------------------------------------------
diff --git a/addons/ot/test/README-speed-ctx b/addons/ot/test/README-speed-ctx
new file mode 100644
index 0000000..fa8fc2c
--- /dev/null
+++ b/addons/ot/test/README-speed-ctx
@@ -0,0 +1,111 @@
+--- rate-limit 100.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency     2.49ms  799.87us  43.00ms   70.90%
+    Req/Sec   393.01     20.61   696.00     71.68%
+  Latency Distribution
+     50%    2.50ms
+     75%    3.00ms
+     90%    3.38ms
+     99%    4.23ms
+  939237 requests in 5.00m, 249.01MB read
+Requests/sec:   3130.01
+Transfer/sec:    849.75KB
+----------------------------------------------------------------------
+
+--- rate-limit 50.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency     1.27ms    0.97ms  40.77ms   56.91%
+    Req/Sec   778.22     70.30     1.36k    69.10%
+  Latency Distribution
+     50%    1.36ms
+     75%    1.80ms
+     90%    2.49ms
+     99%    3.51ms
+  1859055 requests in 5.00m, 492.88MB read
+Requests/sec:   6195.58
+Transfer/sec:      1.64MB
+----------------------------------------------------------------------
+
+--- rate-limit 10.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   442.00us  481.47us  31.61ms   90.27%
+    Req/Sec     2.25k   130.05     2.73k    72.83%
+  Latency Distribution
+     50%  287.00us
+     75%  526.00us
+     90%    0.92ms
+     99%    1.76ms
+  5380213 requests in 5.00m, 1.39GB read
+Requests/sec:  17930.27
+Transfer/sec:      4.75MB
+----------------------------------------------------------------------
+
+--- rate-limit 2.5 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   346.65us  414.65us  28.50ms   95.63%
+    Req/Sec     2.75k   159.74     3.23k    84.68%
+  Latency Distribution
+     50%  271.00us
+     75%  353.00us
+     90%  505.00us
+     99%    1.55ms
+  6560093 requests in 5.00m, 1.70GB read
+Requests/sec:  21864.43
+Transfer/sec:      5.80MB
+----------------------------------------------------------------------
+
+--- rate-limit 0.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   313.32us  402.25us  24.73ms   98.55%
+    Req/Sec     2.95k   145.03     3.21k    88.99%
+  Latency Distribution
+     50%  264.00us
+     75%  327.00us
+     90%  403.00us
+     99%    1.33ms
+  7050847 requests in 5.00m, 1.83GB read
+Requests/sec:  23501.14
+Transfer/sec:      6.23MB
+----------------------------------------------------------------------
+
+--- rate-limit disabled --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   310.19us  384.76us  22.18ms   98.66%
+    Req/Sec     2.96k   115.62     3.37k    84.30%
+  Latency Distribution
+     50%  265.00us
+     75%  327.00us
+     90%  402.00us
+     99%    1.10ms
+  7058682 requests in 5.00m, 1.83GB read
+Requests/sec:  23526.70
+Transfer/sec:      6.24MB
+----------------------------------------------------------------------
+
+--- rate-limit off --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   305.86us  367.56us  25.76ms   98.65%
+    Req/Sec     2.99k   116.93     3.43k    85.59%
+  Latency Distribution
+     50%  261.00us
+     75%  322.00us
+     90%  396.00us
+     99%    1.09ms
+  7137173 requests in 5.00m, 1.85GB read
+Requests/sec:  23788.84
+Transfer/sec:      6.31MB
+----------------------------------------------------------------------
diff --git a/addons/ot/test/README-speed-fe-be b/addons/ot/test/README-speed-fe-be
new file mode 100644
index 0000000..ab2b7af
--- /dev/null
+++ b/addons/ot/test/README-speed-fe-be
@@ -0,0 +1,111 @@
+--- rate-limit 100.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency     0.89ms  466.84us  35.44ms   94.39%
+    Req/Sec     1.09k    39.30     1.32k    72.60%
+  Latency Distribution
+     50%  823.00us
+     75%    1.00ms
+     90%    1.20ms
+     99%    2.14ms
+  2594524 requests in 5.00m, 687.86MB read
+Requests/sec:   8645.83
+Transfer/sec:      2.29MB
+----------------------------------------------------------------------
+
+--- rate-limit 50.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   681.74us  463.28us  20.45ms   95.46%
+    Req/Sec     1.41k    54.00     1.60k    68.97%
+  Latency Distribution
+     50%  613.00us
+     75%  785.00us
+     90%    0.98ms
+     99%    2.06ms
+  3367473 requests in 5.00m, 0.87GB read
+Requests/sec:  11222.76
+Transfer/sec:      2.98MB
+----------------------------------------------------------------------
+
+--- rate-limit 10.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   558.32us  458.54us  29.40ms   97.73%
+    Req/Sec     1.72k    60.67     2.05k    73.10%
+  Latency Distribution
+     50%  494.00us
+     75%  610.00us
+     90%  743.00us
+     99%    2.08ms
+  4105420 requests in 5.00m, 1.06GB read
+Requests/sec:  13683.36
+Transfer/sec:      3.63MB
+----------------------------------------------------------------------
+
+--- rate-limit 2.5 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   542.66us  440.31us  22.63ms   97.88%
+    Req/Sec     1.76k    60.02     2.00k    72.27%
+  Latency Distribution
+     50%  481.00us
+     75%  588.00us
+     90%  710.00us
+     99%    2.05ms
+  4214525 requests in 5.00m, 1.09GB read
+Requests/sec:  14046.76
+Transfer/sec:      3.72MB
+----------------------------------------------------------------------
+
+--- rate-limit 0.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   529.06us  414.38us  30.09ms   97.97%
+    Req/Sec     1.80k    59.34     2.05k    74.47%
+  Latency Distribution
+     50%  473.00us
+     75%  576.00us
+     90%  692.00us
+     99%    1.79ms
+  4287428 requests in 5.00m, 1.11GB read
+Requests/sec:  14290.45
+Transfer/sec:      3.79MB
+----------------------------------------------------------------------
+
+--- rate-limit disabled --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   517.81us  463.10us  36.81ms   98.25%
+    Req/Sec     1.85k    62.39     2.21k    75.65%
+  Latency Distribution
+     50%  458.00us
+     75%  558.00us
+     90%  670.00us
+     99%    1.96ms
+  4416273 requests in 5.00m, 1.14GB read
+Requests/sec:  14719.43
+Transfer/sec:      3.90MB
+----------------------------------------------------------------------
+
+--- rate-limit off --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   511.67us  428.18us  27.68ms   98.15%
+    Req/Sec     1.86k    60.67     2.05k    75.44%
+  Latency Distribution
+     50%  455.00us
+     75%  554.00us
+     90%  666.00us
+     99%    1.81ms
+  4441271 requests in 5.00m, 1.15GB read
+Requests/sec:  14803.32
+Transfer/sec:      3.92MB
+----------------------------------------------------------------------
diff --git a/addons/ot/test/README-speed-sa b/addons/ot/test/README-speed-sa
new file mode 100644
index 0000000..ea8749d
--- /dev/null
+++ b/addons/ot/test/README-speed-sa
@@ -0,0 +1,111 @@
+--- rate-limit 100.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency     1.24ms  522.78us  35.59ms   79.12%
+    Req/Sec   767.71     38.72     3.02k    72.19%
+  Latency Distribution
+     50%    1.20ms
+     75%    1.51ms
+     90%    1.78ms
+     99%    2.37ms
+  1834067 requests in 5.00m, 486.25MB read
+Requests/sec:   6111.57
+Transfer/sec:      1.62MB
+----------------------------------------------------------------------
+
+--- rate-limit 50.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   593.11us  476.81us  43.00ms   91.27%
+    Req/Sec     1.59k    81.15     2.07k    71.14%
+  Latency Distribution
+     50%  549.00us
+     75%  788.00us
+     90%    1.03ms
+     99%    1.62ms
+  3795987 requests in 5.00m, 0.98GB read
+Requests/sec:  12650.65
+Transfer/sec:      3.35MB
+----------------------------------------------------------------------
+
+--- rate-limit 10.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   326.02us  355.00us  29.23ms   98.05%
+    Req/Sec     2.80k    88.05     3.30k    75.36%
+  Latency Distribution
+     50%  277.00us
+     75%  356.00us
+     90%  456.00us
+     99%    0.97ms
+  6675563 requests in 5.00m, 1.73GB read
+Requests/sec:  22249.78
+Transfer/sec:      5.90MB
+----------------------------------------------------------------------
+
+--- rate-limit 2.5 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   311.77us  357.45us  24.11ms   98.62%
+    Req/Sec     2.91k    94.70     3.18k    78.52%
+  Latency Distribution
+     50%  268.00us
+     75%  334.00us
+     90%  413.00us
+     99%    0.94ms
+  6960933 requests in 5.00m, 1.80GB read
+Requests/sec:  23201.07
+Transfer/sec:      6.15MB
+----------------------------------------------------------------------
+
+--- rate-limit 0.0 --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   302.51us  330.50us  25.84ms   98.69%
+    Req/Sec     2.98k    91.46     3.40k    78.84%
+  Latency Distribution
+     50%  263.00us
+     75%  325.00us
+     90%  397.00us
+     99%  812.00us
+  7112084 requests in 5.00m, 1.84GB read
+Requests/sec:  23705.14
+Transfer/sec:      6.28MB
+----------------------------------------------------------------------
+
+--- rate-limit disabled --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   303.01us  353.98us  28.03ms   98.76%
+    Req/Sec     2.99k    93.97     3.34k    81.12%
+  Latency Distribution
+     50%  262.00us
+     75%  323.00us
+     90%  395.00us
+     99%  838.00us
+  7133837 requests in 5.00m, 1.85GB read
+Requests/sec:  23777.95
+Transfer/sec:      6.30MB
+----------------------------------------------------------------------
+
+--- rate-limit off --------------------------------------------------
+Running 5m test @ http://localhost:10080/index.html
+  8 threads and 8 connections
+  Thread Stats   Avg      Stdev     Max   +/- Stdev
+    Latency   302.61us  349.74us  25.48ms   98.75%
+    Req/Sec     2.99k    94.85     3.49k    80.75%
+  Latency Distribution
+     50%  262.00us
+     75%  323.00us
+     90%  395.00us
+     99%  822.00us
+  7132714 requests in 5.00m, 1.85GB read
+Requests/sec:  23773.35
+Transfer/sec:      6.30MB
+----------------------------------------------------------------------
diff --git a/addons/ot/test/be/cfg-dd.json b/addons/ot/test/be/cfg-dd.json
new file mode 100644
index 0000000..8b69b03
--- /dev/null
+++ b/addons/ot/test/be/cfg-dd.json
@@ -0,0 +1,5 @@
+{
+    "service":    "BE",
+    "agent_host": "localhost",
+    "agent_port": 8126
+}
diff --git a/addons/ot/test/be/cfg-jaeger.yml b/addons/ot/test/be/cfg-jaeger.yml
new file mode 100644
index 0000000..0893166
--- /dev/null
+++ b/addons/ot/test/be/cfg-jaeger.yml
@@ -0,0 +1,34 @@
+service_name:
+    BE
+
+###
+# When using configuration object to instantiate the tracer, the type of
+# sampling can be selected via sampler.type and sampler.param properties.
+# Jaeger libraries support the following samplers:
+#
+#  - Constant (sampler.type=const) sampler always makes the same decision for
+#    all traces.  It either samples all traces (sampler.param=1) or none of
+#    them (sampler.param=0).
+#
+#  - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
+#    decision with the probability of sampling equal to the value of
+#    sampler.param property.  For example, with sampler.param=0.1 approximately
+#    1 in 10 traces will be sampled.
+#
+#  - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
+#    limiter to ensure that traces are sampled with a certain constant rate.
+#    For example, when sampler.param=2.0 it will sample requests with the rate
+#    of 2 traces per second.
+#
+#  - Remote (sampler.type=remote, which is also the default) sampler consults
+#    Jaeger agent for the appropriate sampling strategy to use in the current
+#    service.  This allows controlling the sampling strategies in the services
+#    from a central configuration in Jaeger backend, or even dynamically.
+#
+sampler:
+    type: ratelimiting
+    param: 10.0
+
+reporter:
+    logSpans: true
+    localAgentHostPort: localhost:6831
diff --git a/addons/ot/test/be/cfg-zipkin.json b/addons/ot/test/be/cfg-zipkin.json
new file mode 100644
index 0000000..f0e30d5
--- /dev/null
+++ b/addons/ot/test/be/cfg-zipkin.json
@@ -0,0 +1,4 @@
+{
+    "service_name":   "BE",
+    "collector_host": "localhost"
+}
diff --git a/addons/ot/test/be/haproxy.cfg b/addons/ot/test/be/haproxy.cfg
new file mode 100644
index 0000000..c225a2f
--- /dev/null
+++ b/addons/ot/test/be/haproxy.cfg
@@ -0,0 +1,37 @@
+global
+#   nbthread 1
+    maxconn 5000
+    hard-stop-after 10s
+#   log localhost:514 local7 debug
+#   debug
+    stats socket /tmp/haproxy-be.sock mode 666 level admin
+
+defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    option  httpclose
+    retries 3
+    maxconn 4000
+    timeout connect 5000
+    timeout client  50000
+    timeout server  50000
+
+listen stats
+    mode http
+    bind *:8002
+    stats uri /
+    stats admin if TRUE
+    stats refresh 10s
+
+frontend ot-test-be-frontend
+    bind *:11080
+    mode http
+    default_backend servers-backend
+
+    filter opentracing id ot-test-be config be/ot.cfg
+
+backend servers-backend
+    mode http
+    server server-1 127.0.0.1:8000
diff --git a/addons/ot/test/be/ot.cfg b/addons/ot/test/be/ot.cfg
new file mode 100644
index 0000000..edd3f76
--- /dev/null
+++ b/addons/ot/test/be/ot.cfg
@@ -0,0 +1,62 @@
+[ot-test-be]
+    ot-tracer ot-test-tracer
+        config be/cfg-jaeger.yml
+        plugin libjaeger_opentracing_plugin-0.5.0.so
+#       log localhost:514 local7 debug
+        option dontlog-normal
+        option hard-errors
+        no option disabled
+
+        scopes frontend_http_request
+        scopes backend_tcp_request
+        scopes backend_http_request
+        scopes client_session_end
+
+        scopes server_session_start
+        scopes tcp_response
+        scopes http_response
+        scopes server_session_end
+
+    ot-scope frontend_http_request
+        extract "ot-ctx" use-headers
+        span "HAProxy session" child-of "ot-ctx" root
+            baggage "haproxy_id" var(sess.ot.uuid)
+        span "Client session" child-of "HAProxy session"
+        span "Frontend HTTP request" child-of "Client session"
+            tag "http.method" method
+            tag "http.url" url
+            tag "http.version" str("HTTP/") req.ver
+        event on-frontend-http-request
+
+    ot-scope backend_tcp_request
+        span "Backend TCP request" follows-from "Frontend HTTP request"
+        finish "Frontend HTTP request"
+        event on-backend-tcp-request
+
+    ot-scope backend_http_request
+        span "Backend HTTP request" follows-from "Backend TCP request"
+        finish "Backend TCP request"
+        event on-backend-http-request
+
+    ot-scope client_session_end
+        finish "Client session"
+        event on-client-session-end
+
+    ot-scope server_session_start
+        span "Server session" child-of "HAProxy session"
+        finish "Backend HTTP request"
+        event on-server-session-start
+
+    ot-scope tcp_response
+        span "TCP response" child-of "Server session"
+        event on-tcp-response
+
+    ot-scope http_response
+        span "HTTP response" follows-from "TCP response"
+            tag "http.status_code" status
+        finish "TCP response"
+        event on-http-response
+
+    ot-scope server_session_end
+        finish *
+        event on-server-session-end
diff --git a/addons/ot/test/cmp/cfg-dd.json b/addons/ot/test/cmp/cfg-dd.json
new file mode 100644
index 0000000..a931f45
--- /dev/null
+++ b/addons/ot/test/cmp/cfg-dd.json
@@ -0,0 +1,5 @@
+{
+    "service":    "CMP",
+    "agent_host": "localhost",
+    "agent_port": 8126
+}
diff --git a/addons/ot/test/cmp/cfg-jaeger.yml b/addons/ot/test/cmp/cfg-jaeger.yml
new file mode 100644
index 0000000..78efc2d
--- /dev/null
+++ b/addons/ot/test/cmp/cfg-jaeger.yml
@@ -0,0 +1,34 @@
+service_name:
+    CMP
+
+###
+# When using configuration object to instantiate the tracer, the type of
+# sampling can be selected via sampler.type and sampler.param properties.
+# Jaeger libraries support the following samplers:
+#
+#  - Constant (sampler.type=const) sampler always makes the same decision for
+#    all traces.  It either samples all traces (sampler.param=1) or none of
+#    them (sampler.param=0).
+#
+#  - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
+#    decision with the probability of sampling equal to the value of
+#    sampler.param property.  For example, with sampler.param=0.1 approximately
+#    1 in 10 traces will be sampled.
+#
+#  - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
+#    limiter to ensure that traces are sampled with a certain constant rate.
+#    For example, when sampler.param=2.0 it will sample requests with the rate
+#    of 2 traces per second.
+#
+#  - Remote (sampler.type=remote, which is also the default) sampler consults
+#    Jaeger agent for the appropriate sampling strategy to use in the current
+#    service.  This allows controlling the sampling strategies in the services
+#    from a central configuration in Jaeger backend, or even dynamically.
+#
+sampler:
+    type: ratelimiting
+    param: 10.0
+
+reporter:
+    logSpans: true
+    localAgentHostPort: localhost:6831
diff --git a/addons/ot/test/cmp/cfg-zipkin.json b/addons/ot/test/cmp/cfg-zipkin.json
new file mode 100644
index 0000000..7e9d3dd
--- /dev/null
+++ b/addons/ot/test/cmp/cfg-zipkin.json
@@ -0,0 +1,4 @@
+{
+    "service_name":   "CMP",
+    "collector_host": "localhost"
+}
diff --git a/addons/ot/test/cmp/haproxy.cfg b/addons/ot/test/cmp/haproxy.cfg
new file mode 100644
index 0000000..9d22725
--- /dev/null
+++ b/addons/ot/test/cmp/haproxy.cfg
@@ -0,0 +1,36 @@
+global
+#   nbthread 1
+    maxconn 5000
+    hard-stop-after 10s
+    stats socket /tmp/haproxy.sock mode 666 level admin
+
+defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    option  httpclose
+    retries 3
+    maxconn 4000
+    timeout connect 5000
+    timeout client  50000
+    timeout server  50000
+
+listen stats
+    mode http
+    bind *:8001
+    stats uri /
+    stats admin if TRUE
+    stats refresh 10s
+
+frontend ot-test-cmp-frontend
+    bind *:10080
+    mode http
+    default_backend servers-backend
+
+    acl acl-http-status-ok status 100:399
+    filter opentracing id ot-test-cmp config cmp/ot.cfg
+
+backend servers-backend
+    mode http
+    server server-1 127.0.0.1:8000
diff --git a/addons/ot/test/cmp/ot.cfg b/addons/ot/test/cmp/ot.cfg
new file mode 100644
index 0000000..21b15dd
--- /dev/null
+++ b/addons/ot/test/cmp/ot.cfg
@@ -0,0 +1,83 @@
+[ot-test-cmp]
+    ot-tracer ot-test-tracer
+        config cmp/cfg-jaeger.yml
+        plugin libjaeger_opentracing_plugin-0.5.0.so
+#       log localhost:514 local7 debug
+        option dontlog-normal
+        option hard-errors
+        no option disabled
+        rate-limit 100.0
+
+        scopes client_session_start
+        scopes frontend_tcp_request
+        scopes frontend_http_request
+        scopes backend_tcp_request
+        scopes backend_http_request
+        scopes server_unavailable
+
+        scopes server_session_start
+        scopes tcp_response
+        scopes http_response http_response-error server_session_end client_session_end
+
+    ot-scope client_session_start
+        span "HAProxy session" root
+            baggage "haproxy_id" var(sess.ot.uuid)
+        span "Client session" child-of "HAProxy session"
+        event on-client-session-start
+
+    ot-scope frontend_tcp_request
+        span "Frontend TCP request" child-of "Client session"
+        event on-frontend-tcp-request
+
+    ot-scope frontend_http_request
+        span "Frontend HTTP request" follows-from "Frontend TCP request"
+            tag "http.method" method
+            tag "http.url" url
+            tag "http.version" str("HTTP/") req.ver
+        finish "Frontend TCP request"
+        event on-frontend-http-request
+
+    ot-scope backend_tcp_request
+        span "Backend TCP request" follows-from "Frontend HTTP request"
+        finish "Frontend HTTP request"
+        event on-backend-tcp-request
+
+    ot-scope backend_http_request
+        span "Backend HTTP request" follows-from "Backend TCP request"
+        finish "Backend TCP request"
+        event on-backend-http-request
+
+    ot-scope server_unavailable
+        span "HAProxy session"
+            tag "error" bool(true)
+            log "status" str("503 Service Unavailable")
+        finish *
+        event on-server-unavailable
+
+    ot-scope server_session_start
+        span "Server session" child-of "HAProxy session"
+        finish "Backend HTTP request"
+        event on-server-session-start
+
+    ot-scope tcp_response
+        span "TCP response" child-of "Server session"
+        event on-tcp-response
+
+    ot-scope http_response
+        span "HTTP response" follows-from "TCP response"
+            tag "http.status_code" status
+        finish "TCP response"
+        event on-http-response
+
+    ot-scope http_response-error
+        span "HTTP response"
+            tag "error" bool(true)
+        event on-http-response if !acl-http-status-ok
+
+    ot-scope server_session_end
+        finish "HTTP response" "Server session"
+        event on-http-response
+
+    ot-scope client_session_end
+        finish "*"
+        event on-http-response
diff --git a/addons/ot/test/ctx/cfg-dd.json b/addons/ot/test/ctx/cfg-dd.json
new file mode 100644
index 0000000..f68d97a
--- /dev/null
+++ b/addons/ot/test/ctx/cfg-dd.json
@@ -0,0 +1,5 @@
+{
+    "service":    "CTX",
+    "agent_host": "localhost",
+    "agent_port": 8126
+}
diff --git a/addons/ot/test/ctx/cfg-jaeger.yml b/addons/ot/test/ctx/cfg-jaeger.yml
new file mode 100644
index 0000000..659724a
--- /dev/null
+++ b/addons/ot/test/ctx/cfg-jaeger.yml
@@ -0,0 +1,34 @@
+service_name:
+    CTX
+
+###
+# When using configuration object to instantiate the tracer, the type of
+# sampling can be selected via sampler.type and sampler.param properties.
+# Jaeger libraries support the following samplers:
+#
+#  - Constant (sampler.type=const) sampler always makes the same decision for
+#    all traces.  It either samples all traces (sampler.param=1) or none of
+#    them (sampler.param=0).
+#
+#  - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
+#    decision with the probability of sampling equal to the value of
+#    sampler.param property.  For example, with sampler.param=0.1 approximately
+#    1 in 10 traces will be sampled.
+#
+#  - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
+#    limiter to ensure that traces are sampled with a certain constant rate.
+#    For example, when sampler.param=2.0 it will sample requests with the rate
+#    of 2 traces per second.
+#
+#  - Remote (sampler.type=remote, which is also the default) sampler consults
+#    Jaeger agent for the appropriate sampling strategy to use in the current
+#    service.  This allows controlling the sampling strategies in the services
+#    from a central configuration in Jaeger backend, or even dynamically.
+#
+sampler:
+    type: ratelimiting
+    param: 10.0
+
+reporter:
+    logSpans: true
+    localAgentHostPort: localhost:6831
diff --git a/addons/ot/test/ctx/cfg-zipkin.json b/addons/ot/test/ctx/cfg-zipkin.json
new file mode 100644
index 0000000..3a3a257
--- /dev/null
+++ b/addons/ot/test/ctx/cfg-zipkin.json
@@ -0,0 +1,4 @@
+{
+    "service_name":   "CTX",
+    "collector_host": "localhost"
+}
diff --git a/addons/ot/test/ctx/haproxy.cfg b/addons/ot/test/ctx/haproxy.cfg
new file mode 100644
index 0000000..d240a99
--- /dev/null
+++ b/addons/ot/test/ctx/haproxy.cfg
@@ -0,0 +1,38 @@
+global
+#   nbthread 1
+    maxconn 5000
+    hard-stop-after 10s
+    stats socket /tmp/haproxy.sock mode 666 level admin
+
+defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    option  httpclose
+    retries 3
+    maxconn 4000
+    timeout connect 5000
+    timeout client  50000
+    timeout server  50000
+
+listen stats
+    mode http
+    bind *:8001
+    stats uri /
+    stats admin if TRUE
+    stats refresh 10s
+
+frontend ot-test-ctx-frontend
+    bind *:10080
+    mode http
+    default_backend servers-backend
+
+    acl acl-http-status-ok status 100:399
+    filter opentracing id ot-test-ctx config ctx/ot.cfg
+    http-response ot-group ot-test-ctx http_response_group if acl-http-status-ok
+    http-after-response ot-group ot-test-ctx http_after_response_group if !acl-http-status-ok
+
+backend servers-backend
+    mode http
+    server server-1 127.0.0.1:8000
diff --git a/addons/ot/test/ctx/ot.cfg b/addons/ot/test/ctx/ot.cfg
new file mode 100644
index 0000000..a06a4e0
--- /dev/null
+++ b/addons/ot/test/ctx/ot.cfg
@@ -0,0 +1,197 @@
+[ot-test-ctx]
+    ot-tracer ot-test-tracer
+        log localhost:514 local7 debug
+        config ctx/cfg-jaeger.yml
+        plugin libjaeger_opentracing_plugin-0.5.0.so
+        option dontlog-normal
+        option hard-errors
+        no option disabled
+        rate-limit 100.0
+
+        groups http_response_group
+        groups http_after_response_group
+
+        scopes client_session_start_1
+        scopes client_session_start_2
+        scopes frontend_tcp_request
+        scopes http_wait_request
+        scopes http_body_request
+        scopes frontend_http_request
+        scopes switching_rules_request
+        scopes backend_tcp_request
+        scopes backend_http_request
+        scopes process_server_rules_request
+        scopes http_process_request
+        scopes tcp_rdp_cookie_request
+        scopes process_sticking_rules_request
+        scopes client_session_end
+        scopes server_unavailable
+
+        scopes server_session_start
+        scopes tcp_response
+        scopes http_wait_response
+        scopes process_store_rules_response
+        scopes http_response http_response-error
+        scopes server_session_end
+
+    ot-group http_response_group
+        scopes http_response_1
+        scopes http_response_2
+
+    ot-scope http_response_1
+        span "HTTP response"
+            log "hdr.content" res.hdr("content-type") str("; length: ") res.hdr("content-length") str(" bytes")
+
+    ot-scope http_response_2
+        span "HTTP response"
+            log "hdr.date" res.hdr("date") str(" / ") res.hdr("last-modified")
+
+    ot-group http_after_response_group
+        scopes http_after_response
+
+    ot-scope http_after_response
+        span "HAProxy response" child-of "HAProxy session"
+            tag "error" bool(true)
+            tag "http.status_code" status
+
+    ot-scope client_session_start_1
+        span "HAProxy session" root
+            inject "ot_ctx_1" use-headers use-vars
+            baggage "haproxy_id" var(sess.ot.uuid)
+        event on-client-session-start
+
+    ot-scope client_session_start_2
+        extract "ot_ctx_1" use-vars
+        span "Client session" child-of "ot_ctx_1"
+            inject "ot_ctx_2" use-headers use-vars
+        event on-client-session-start
+
+    ot-scope frontend_tcp_request
+        extract "ot_ctx_2" use-vars
+        span "Frontend TCP request" child-of "ot_ctx_2"
+            inject "ot_ctx_3" use-headers use-vars
+        event on-frontend-tcp-request
+
+    ot-scope http_wait_request
+        extract "ot_ctx_3" use-vars
+        span "HTTP wait request" follows-from "ot_ctx_3"
+            inject "ot_ctx_4" use-headers use-vars
+        finish "Frontend TCP request" "ot_ctx_3"
+        event on-http-wait-request
+
+    ot-scope http_body_request
+        extract "ot_ctx_4" use-vars
+        span "HTTP body request" follows-from "ot_ctx_4"
+            inject "ot_ctx_5" use-headers use-vars
+        finish "HTTP wait request" "ot_ctx_4"
+        event on-http-body-request
+
+    ot-scope frontend_http_request
+        extract "ot_ctx_5" use-vars
+        span "Frontend HTTP request" follows-from "ot_ctx_5"
+            tag "http.method" method
+            tag "http.url" url
+            tag "http.version" str("HTTP/") req.ver
+            inject "ot_ctx_6" use-headers use-vars
+        finish "HTTP body request" "ot_ctx_5"
+        event on-frontend-http-request
+
+    ot-scope switching_rules_request
+        extract "ot_ctx_6" use-vars
+        span "Switching rules request" follows-from "ot_ctx_6"
+            inject "ot_ctx_7" use-headers use-vars
+        finish "Frontend HTTP request" "ot_ctx_6"
+        event on-switching-rules-request
+
+    ot-scope backend_tcp_request
+        extract "ot_ctx_7" use-vars
+        span "Backend TCP request" follows-from "ot_ctx_7"
+            inject "ot_ctx_8" use-headers use-vars
+        finish "Switching rules request" "ot_ctx_7"
+        event on-backend-tcp-request
+
+    ot-scope backend_http_request
+        extract "ot_ctx_8" use-vars
+        span "Backend HTTP request" follows-from "ot_ctx_8"
+            inject "ot_ctx_9" use-headers use-vars
+        finish "Backend TCP request" "ot_ctx_8"
+        event on-backend-http-request
+
+    ot-scope process_server_rules_request
+        extract "ot_ctx_9" use-vars
+        span "Process server rules request" follows-from "ot_ctx_9"
+            inject "ot_ctx_10" use-headers use-vars
+        finish "Backend HTTP request" "ot_ctx_9"
+        event on-process-server-rules-request
+
+    ot-scope http_process_request
+        extract "ot_ctx_10" use-vars
+        span "HTTP process request" follows-from "ot_ctx_10"
+            inject "ot_ctx_11" use-headers use-vars
+        finish "Process server rules request" "ot_ctx_10"
+        event on-http-process-request
+
+    ot-scope tcp_rdp_cookie_request
+        extract "ot_ctx_11" use-vars
+        span "TCP RDP cookie request" follows-from "ot_ctx_11"
+            inject "ot_ctx_12" use-headers use-vars
+        finish "HTTP process request" "ot_ctx_11"
+        event on-tcp-rdp-cookie-request
+
+    ot-scope process_sticking_rules_request
+        extract "ot_ctx_12" use-vars
+        span "Process sticking rules request" follows-from "ot_ctx_12"
+            inject "ot_ctx_13" use-headers use-vars
+        finish "TCP RDP cookie request" "ot_ctx_12"
+        event on-process-sticking-rules-request
+
+    ot-scope client_session_end
+        finish "Client session" "ot_ctx_2"
+        event on-client-session-end
+
+    ot-scope server_unavailable
+        finish *
+        event on-server-unavailable
+
+    ot-scope server_session_start
+        span "Server session" child-of "ot_ctx_1"
+            inject "ot_ctx_14" use-vars
+        extract "ot_ctx_13" use-vars
+        finish "Process sticking rules request" "ot_ctx_13"
+        event on-server-session-start
+
+    ot-scope tcp_response
+        extract "ot_ctx_14" use-vars
+        span "TCP response" child-of "ot_ctx_14"
+            inject "ot_ctx_15" use-vars
+        event on-tcp-response
+
+    ot-scope http_wait_response
+        extract "ot_ctx_15" use-vars
+        span "HTTP wait response" follows-from "ot_ctx_15"
+            inject "ot_ctx_16" use-headers use-vars
+        finish "TCP response" "ot_ctx_15"
+        event on-http-wait-response
+
+    ot-scope process_store_rules_response
+        extract "ot_ctx_16" use-vars
+        span "Process store rules response" follows-from "ot_ctx_16"
+            inject "ot_ctx_17" use-headers use-vars
+        finish "HTTP wait response" "ot_ctx_16"
+        event on-process-store-rules-response
+
+    ot-scope http_response
+        extract "ot_ctx_17" use-vars
+        span "HTTP response" follows-from "ot_ctx_17"
+            tag "http.status_code" status
+        finish "Process store rules response" "ot_ctx_17"
+        event on-http-response
+
+    ot-scope http_response-error
+        span "HTTP response"
+            tag "error" bool(true)
+        event on-http-response if !acl-http-status-ok
+
+    ot-scope server_session_end
+        finish *
+        event on-server-session-end
diff --git a/addons/ot/test/empty/cfg-dd.json b/addons/ot/test/empty/cfg-dd.json
new file mode 100644
index 0000000..38b65f1
--- /dev/null
+++ b/addons/ot/test/empty/cfg-dd.json
@@ -0,0 +1,5 @@
+{
+    "service":    "EMPTY",
+    "agent_host": "localhost",
+    "agent_port": 8126
+}
diff --git a/addons/ot/test/empty/cfg-jaeger.yml b/addons/ot/test/empty/cfg-jaeger.yml
new file mode 100644
index 0000000..08fadd8
--- /dev/null
+++ b/addons/ot/test/empty/cfg-jaeger.yml
@@ -0,0 +1,34 @@
+service_name:
+    EMPTY
+
+###
+# When using configuration object to instantiate the tracer, the type of
+# sampling can be selected via sampler.type and sampler.param properties.
+# Jaeger libraries support the following samplers:
+#
+#  - Constant (sampler.type=const) sampler always makes the same decision for
+#    all traces.  It either samples all traces (sampler.param=1) or none of
+#    them (sampler.param=0).
+#
+#  - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
+#    decision with the probability of sampling equal to the value of
+#    sampler.param property.  For example, with sampler.param=0.1 approximately
+#    1 in 10 traces will be sampled.
+#
+#  - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
+#    limiter to ensure that traces are sampled with a certain constant rate.
+#    For example, when sampler.param=2.0 it will sample requests with the rate
+#    of 2 traces per second.
+#
+#  - Remote (sampler.type=remote, which is also the default) sampler consults
+#    Jaeger agent for the appropriate sampling strategy to use in the current
+#    service.  This allows controlling the sampling strategies in the services
+#    from a central configuration in Jaeger backend, or even dynamically.
+#
+sampler:
+    type: ratelimiting
+    param: 10.0
+
+reporter:
+    logSpans: true
+    localAgentHostPort: localhost:6831
diff --git a/addons/ot/test/empty/cfg-zipkin.json b/addons/ot/test/empty/cfg-zipkin.json
new file mode 100644
index 0000000..55fde9f
--- /dev/null
+++ b/addons/ot/test/empty/cfg-zipkin.json
@@ -0,0 +1,4 @@
+{
+    "service_name":   "EMPTY",
+    "collector_host": "localhost"
+}
diff --git a/addons/ot/test/empty/haproxy.cfg b/addons/ot/test/empty/haproxy.cfg
new file mode 100644
index 0000000..9d40db9
--- /dev/null
+++ b/addons/ot/test/empty/haproxy.cfg
@@ -0,0 +1,30 @@
+global
+    stats socket /tmp/haproxy.sock mode 666 level admin
+
+defaults
+    log    global
+    mode   http
+    option httplog
+    option dontlognull
+    option httpclose
+    timeout connect 5000
+    timeout client  50000
+    timeout server  50000
+
+listen stats
+    mode http
+    bind *:8001
+    stats uri /
+    stats admin if TRUE
+    stats refresh 10s
+
+frontend ot-test-empty
+    bind *:10080
+    mode http
+    default_backend servers-backend
+
+    filter opentracing id ot-test-empty config empty/ot.cfg
+
+backend servers-backend
+    mode http
+    server server-1 127.0.0.1:8000
diff --git a/addons/ot/test/empty/ot.cfg b/addons/ot/test/empty/ot.cfg
new file mode 100644
index 0000000..961c8bc
--- /dev/null
+++ b/addons/ot/test/empty/ot.cfg
@@ -0,0 +1,3 @@
+ot-tracer ot-test-tracer
+    config empty/cfg-jaeger.yml
+    plugin libjaeger_opentracing_plugin-0.5.0.so
diff --git a/addons/ot/test/fe/cfg-dd.json b/addons/ot/test/fe/cfg-dd.json
new file mode 100644
index 0000000..84afe56
--- /dev/null
+++ b/addons/ot/test/fe/cfg-dd.json
@@ -0,0 +1,5 @@
+{
+    "service":    "FE",
+    "agent_host": "localhost",
+    "agent_port": 8126
+}
diff --git a/addons/ot/test/fe/cfg-jaeger.yml b/addons/ot/test/fe/cfg-jaeger.yml
new file mode 100644
index 0000000..1365efa
--- /dev/null
+++ b/addons/ot/test/fe/cfg-jaeger.yml
@@ -0,0 +1,34 @@
+service_name:
+    FE
+
+###
+# When using configuration object to instantiate the tracer, the type of
+# sampling can be selected via sampler.type and sampler.param properties.
+# Jaeger libraries support the following samplers:
+#
+#  - Constant (sampler.type=const) sampler always makes the same decision for
+#    all traces.  It either samples all traces (sampler.param=1) or none of
+#    them (sampler.param=0).
+#
+#  - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
+#    decision with the probability of sampling equal to the value of
+#    sampler.param property.  For example, with sampler.param=0.1 approximately
+#    1 in 10 traces will be sampled.
+#
+#  - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
+#    limiter to ensure that traces are sampled with a certain constant rate.
+#    For example, when sampler.param=2.0 it will sample requests with the rate
+#    of 2 traces per second.
+#
+#  - Remote (sampler.type=remote, which is also the default) sampler consults
+#    Jaeger agent for the appropriate sampling strategy to use in the current
+#    service.  This allows controlling the sampling strategies in the services
+#    from a central configuration in Jaeger backend, or even dynamically.
+#
+sampler:
+    type: ratelimiting
+    param: 10.0
+
+reporter:
+    logSpans: true
+    localAgentHostPort: localhost:6831
diff --git a/addons/ot/test/fe/cfg-zipkin.json b/addons/ot/test/fe/cfg-zipkin.json
new file mode 100644
index 0000000..1546b10
--- /dev/null
+++ b/addons/ot/test/fe/cfg-zipkin.json
@@ -0,0 +1,4 @@
+{
+    "service_name":   "FE",
+    "collector_host": "localhost"
+}
diff --git a/addons/ot/test/fe/haproxy.cfg b/addons/ot/test/fe/haproxy.cfg
new file mode 100644
index 0000000..bfc0ec9
--- /dev/null
+++ b/addons/ot/test/fe/haproxy.cfg
@@ -0,0 +1,37 @@
+global
+#   nbthread 1
+    maxconn 5000
+    hard-stop-after 10s
+#   log localhost:514 local7 debug
+#   debug
+    stats socket /tmp/haproxy-fe.sock mode 666 level admin
+
+defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    option  httpclose
+    retries 3
+    maxconn 4000
+    timeout connect 5000
+    timeout client  50000
+    timeout server  50000
+
+listen stats
+    mode http
+    bind *:8001
+    stats uri /
+    stats admin if TRUE
+    stats refresh 10s
+
+frontend ot-test-fe-frontend
+    bind *:10080
+    mode http
+    default_backend servers-backend
+
+    filter opentracing id ot-test-fe config fe/ot.cfg
+
+backend servers-backend
+    mode http
+    server server-1 127.0.0.1:11080
diff --git a/addons/ot/test/fe/ot.cfg b/addons/ot/test/fe/ot.cfg
new file mode 100644
index 0000000..11de828
--- /dev/null
+++ b/addons/ot/test/fe/ot.cfg
@@ -0,0 +1,74 @@
+[ot-test-fe]
+    ot-tracer ot-test-tracer
+        config fe/cfg-jaeger.yml
+        plugin libjaeger_opentracing_plugin-0.5.0.so
+#       log localhost:514 local7 debug
+        option dontlog-normal
+        option hard-errors
+        no option disabled
+        rate-limit 100.0
+
+        scopes client_session_start
+        scopes frontend_tcp_request
+        scopes frontend_http_request
+        scopes backend_tcp_request
+        scopes backend_http_request
+        scopes client_session_end
+
+        scopes server_session_start
+        scopes tcp_response
+        scopes http_response
+        scopes server_session_end
+
+    ot-scope client_session_start
+        span "HAProxy session" root
+            baggage "haproxy_id" var(sess.ot.uuid)
+        span "Client session" child-of "HAProxy session"
+        event on-client-session-start
+
+    ot-scope frontend_tcp_request
+        span "Frontend TCP request" child-of "Client session"
+        event on-frontend-tcp-request
+
+    ot-scope frontend_http_request
+        span "Frontend HTTP request" follows-from "Frontend TCP request"
+            tag "http.method" method
+            tag "http.url" url
+            tag "http.version" str("HTTP/") req.ver
+        finish "Frontend TCP request"
+        event on-frontend-http-request
+
+    ot-scope backend_tcp_request
+        span "Backend TCP request" follows-from "Frontend HTTP request"
+        finish "Frontend HTTP request"
+        event on-backend-tcp-request
+
+    ot-scope backend_http_request
+        span "Backend HTTP request" follows-from "Backend TCP request"
+        finish "Backend TCP request"
+        span "HAProxy session"
+            inject "ot-ctx" use-headers
+        event on-backend-http-request
+
+    ot-scope client_session_end
+        finish "Client session"
+        event on-client-session-end
+
+    ot-scope server_session_start
+        span "Server session" child-of "HAProxy session"
+        finish "Backend HTTP request"
+        event on-server-session-start
+
+    ot-scope tcp_response
+        span "TCP response" child-of "Server session"
+        event on-tcp-response
+
+    ot-scope http_response
+        span "HTTP response" follows-from "TCP response"
+            tag "http.status_code" status
+        finish "TCP response"
+        event on-http-response
+
+    ot-scope server_session_end
+        finish *
+        event on-server-session-end
diff --git a/addons/ot/test/func-stat.sh b/addons/ot/test/func-stat.sh
new file mode 100755
index 0000000..cf5bd9e
--- /dev/null
+++ b/addons/ot/test/func-stat.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+#
+test ${#} -lt 1 && exit 1
+
+awk '/ {$/ { sub(/\(.*/, "", $5); print $5 }' "${@}" | sort | uniq -c
diff --git a/addons/ot/test/get-opentracing-plugins.sh b/addons/ot/test/get-opentracing-plugins.sh
new file mode 100755
index 0000000..f2fe2d6
--- /dev/null
+++ b/addons/ot/test/get-opentracing-plugins.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+_ARG_DIR="${1:-.}"
+
+
+get ()
+{
+	local _arg_tracer="${1}"
+	local _arg_version="${2}"
+	local _arg_url="${3}"
+	local _arg_file="${4}"
+	local _var_tmpfile="_tmpfile_"
+	local _var_plugin="lib${_arg_tracer}_opentracing_plugin-${_arg_version}.so"
+
+	test -e "${_var_plugin}" && return 0
+
+	wget "https://github.com/${_arg_url}/releases/download/v${_arg_version}/${_arg_file}" -O "${_var_tmpfile}" || {
+		rm "${_var_tmpfile}"
+		return 1
+	}
+
+	case "$(file ${_var_tmpfile})" in
+	  *shared\ object*)
+		mv "${_var_tmpfile}" "${_var_plugin}" ;;
+
+	  *gzip\ compressed\ data*)
+		gzip -cd "${_var_tmpfile}" > "${_var_plugin}"
+		rm "${_var_tmpfile}" ;;
+	esac
+}
+
+
+mkdir -p "${_ARG_DIR}" && cd "${_ARG_DIR}" || exit 1
+
+get dd 1.1.2 DataDog/dd-opentracing-cpp linux-amd64-libdd_opentracing_plugin.so.gz
+get dd 1.2.0 DataDog/dd-opentracing-cpp linux-amd64-libdd_opentracing_plugin.so.gz
+
+get jaeger 0.4.2 jaegertracing/jaeger-client-cpp libjaegertracing_plugin.linux_amd64.so
+#et jaeger 0.5.0 jaegertracing/jaeger-client-cpp libjaegertracing_plugin.linux_amd64.so
+#et jaeger 0.6.0 jaegertracing/jaeger-client-cpp libjaegertracing_plugin.linux_amd64.so
+
+get lightstep 0.12.0 lightstep/lightstep-tracer-cpp linux-amd64-liblightstep_tracer_plugin.so.gz
+get lightstep 0.13.0 lightstep/lightstep-tracer-cpp linux-amd64-liblightstep_tracer_plugin.so.gz
+
+get zipkin 0.5.2 rnburn/zipkin-cpp-opentracing linux-amd64-libzipkin_opentracing_plugin.so.gz
diff --git a/addons/ot/test/index.html b/addons/ot/test/index.html
new file mode 100644
index 0000000..09ed6fa
--- /dev/null
+++ b/addons/ot/test/index.html
@@ -0,0 +1 @@
+<html><body><p>Did I err?</p></body></html>
diff --git a/addons/ot/test/run-cmp.sh b/addons/ot/test/run-cmp.sh
new file mode 100755
index 0000000..77b48bd
--- /dev/null
+++ b/addons/ot/test/run-cmp.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
+       _ARGS="-f cmp/haproxy.cfg"
+    _LOG_DIR="_logs"
+        _LOG="${_LOG_DIR}/_log-$(basename ${0} .sh)-$(date +%s)"
+
+
+test -x "${_ARG_HAPROXY}" || exit 1
+mkdir -p "${_LOG_DIR}"    || exit 2
+
+echo "executing: ${_ARG_HAPROXY} ${_ARGS} > ${_LOG}"
+"${_ARG_HAPROXY}" ${_ARGS} >"${_LOG}" 2>&1
diff --git a/addons/ot/test/run-ctx.sh b/addons/ot/test/run-ctx.sh
new file mode 100755
index 0000000..064fa7d
--- /dev/null
+++ b/addons/ot/test/run-ctx.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
+       _ARGS="-f ctx/haproxy.cfg"
+    _LOG_DIR="_logs"
+        _LOG="${_LOG_DIR}/_log-$(basename ${0} .sh)-$(date +%s)"
+
+
+test -x "${_ARG_HAPROXY}" || exit 1
+mkdir -p "${_LOG_DIR}"    || exit 2
+
+echo "executing: ${_ARG_HAPROXY} ${_ARGS} > ${_LOG}"
+"${_ARG_HAPROXY}" ${_ARGS} >"${_LOG}" 2>&1
diff --git a/addons/ot/test/run-fe-be.sh b/addons/ot/test/run-fe-be.sh
new file mode 100755
index 0000000..7e70ad6
--- /dev/null
+++ b/addons/ot/test/run-fe-be.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
+    _ARGS_FE="-f fe/haproxy.cfg"
+    _ARGS_BE="-f be/haproxy.cfg"
+       _TIME="$(date +%s)"
+    _LOG_DIR="_logs"
+     _LOG_FE="${_LOG_DIR}/_log-$(basename ${0} fe-be.sh)fe-${_TIME}"
+     _LOG_BE="${_LOG_DIR}/_log-$(basename ${0} fe-be.sh)be-${_TIME}"
+
+
+__exit ()
+{
+	test -z "${2}" && {
+		echo
+		echo "Script killed!"
+
+		echo "Waiting for jobs to complete..."
+		pkill --signal SIGUSR1 haproxy
+		wait
+	}
+
+	test -n "${1}" && {
+		echo
+		echo "${1}"
+		echo
+	}
+
+	exit ${2:-100}
+}
+
+
+trap __exit INT TERM
+
+test -x "${_ARG_HAPROXY}" || __exit "${_ARG_HAPROXY}: executable does not exist" 1
+mkdir -p "${_LOG_DIR}"    || __exit "${_ARG_HAPROXY}: cannot create log directory" 2
+
+echo "\n------------------------------------------------------------------------"
+echo "--- executing: ${_ARG_HAPROXY} ${_ARGS_BE} > ${_LOG_BE}"
+"${_ARG_HAPROXY}" ${_ARGS_BE} >"${_LOG_BE}" 2>&1 &
+
+echo "--- executing: ${_ARG_HAPROXY} ${_ARGS_FE} > ${_LOG_FE}"
+"${_ARG_HAPROXY}" ${_ARGS_FE} >"${_LOG_FE}" 2>&1 &
+echo "------------------------------------------------------------------------\n"
+
+echo "Press CTRL-C to quit..."
+wait
diff --git a/addons/ot/test/run-sa.sh b/addons/ot/test/run-sa.sh
new file mode 100755
index 0000000..e5682ea
--- /dev/null
+++ b/addons/ot/test/run-sa.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+#
+_ARG_HAPROXY="${1:-$(realpath -L ${PWD}/../../../haproxy)}"
+       _ARGS="-f sa/haproxy.cfg"
+    _LOG_DIR="_logs"
+        _LOG="${_LOG_DIR}/_log-$(basename ${0} .sh)-$(date +%s)"
+
+
+test -x "${_ARG_HAPROXY}" || exit 1
+mkdir -p "${_LOG_DIR}"    || exit 2
+
+echo "executing: ${_ARG_HAPROXY} ${_ARGS} > ${_LOG}"
+"${_ARG_HAPROXY}" ${_ARGS} >"${_LOG}" 2>&1
diff --git a/addons/ot/test/sa/cfg-dd.json b/addons/ot/test/sa/cfg-dd.json
new file mode 100644
index 0000000..0c476f7
--- /dev/null
+++ b/addons/ot/test/sa/cfg-dd.json
@@ -0,0 +1,5 @@
+{
+    "service":    "SA",
+    "agent_host": "localhost",
+    "agent_port": 8126
+}
diff --git a/addons/ot/test/sa/cfg-jaeger.yml b/addons/ot/test/sa/cfg-jaeger.yml
new file mode 100644
index 0000000..e14f91e
--- /dev/null
+++ b/addons/ot/test/sa/cfg-jaeger.yml
@@ -0,0 +1,34 @@
+service_name:
+    SA
+
+###
+# When using configuration object to instantiate the tracer, the type of
+# sampling can be selected via sampler.type and sampler.param properties.
+# Jaeger libraries support the following samplers:
+#
+#  - Constant (sampler.type=const) sampler always makes the same decision for
+#    all traces.  It either samples all traces (sampler.param=1) or none of
+#    them (sampler.param=0).
+#
+#  - Probabilistic (sampler.type=probabilistic) sampler makes a random sampling
+#    decision with the probability of sampling equal to the value of
+#    sampler.param property.  For example, with sampler.param=0.1 approximately
+#    1 in 10 traces will be sampled.
+#
+#  - Rate Limiting (sampler.type=ratelimiting) sampler uses a leaky bucket rate
+#    limiter to ensure that traces are sampled with a certain constant rate.
+#    For example, when sampler.param=2.0 it will sample requests with the rate
+#    of 2 traces per second.
+#
+#  - Remote (sampler.type=remote, which is also the default) sampler consults
+#    Jaeger agent for the appropriate sampling strategy to use in the current
+#    service.  This allows controlling the sampling strategies in the services
+#    from a central configuration in Jaeger backend, or even dynamically.
+#
+sampler:
+    type: ratelimiting
+    param: 10.0
+
+reporter:
+    logSpans: true
+    localAgentHostPort: localhost:6831
diff --git a/addons/ot/test/sa/cfg-zipkin.json b/addons/ot/test/sa/cfg-zipkin.json
new file mode 100644
index 0000000..9d155ba
--- /dev/null
+++ b/addons/ot/test/sa/cfg-zipkin.json
@@ -0,0 +1,4 @@
+{
+    "service_name":   "SA",
+    "collector_host": "localhost"
+}
diff --git a/addons/ot/test/sa/haproxy.cfg b/addons/ot/test/sa/haproxy.cfg
new file mode 100644
index 0000000..988e3ab
--- /dev/null
+++ b/addons/ot/test/sa/haproxy.cfg
@@ -0,0 +1,40 @@
+global
+#   nbthread 1
+    maxconn 5000
+    hard-stop-after 10s
+#   log localhost:514 local7 debug
+#   debug
+    stats socket /tmp/haproxy.sock mode 666 level admin
+
+defaults
+    log     global
+    mode    http
+    option  httplog
+    option  dontlognull
+    option  httpclose
+    retries 3
+    maxconn 4000
+    timeout connect 5000
+    timeout client  50000
+    timeout server  50000
+
+listen stats
+    mode http
+    bind *:8001
+    stats uri /
+    stats admin if TRUE
+    stats refresh 10s
+
+frontend ot-test-sa-frontend
+    bind *:10080
+    mode http
+    default_backend servers-backend
+
+    acl acl-http-status-ok status 100:399
+    filter opentracing id ot-test-sa config sa/ot.cfg
+    http-response ot-group ot-test-sa http_response_group if acl-http-status-ok
+    http-after-response ot-group ot-test-sa http_after_response_group if !acl-http-status-ok
+
+backend servers-backend
+    mode http
+    server server-1 127.0.0.1:8000
diff --git a/addons/ot/test/sa/ot.cfg b/addons/ot/test/sa/ot.cfg
new file mode 100644
index 0000000..ae7413b
--- /dev/null
+++ b/addons/ot/test/sa/ot.cfg
@@ -0,0 +1,160 @@
+[ot-test-sa]
+    ot-tracer ot-test-tracer
+        log localhost:514 local7 debug
+        config sa/cfg-jaeger.yml
+        plugin libjaeger_opentracing_plugin-0.5.0.so
+        option dontlog-normal
+        option hard-errors
+        no option disabled
+        rate-limit 100.0
+
+        groups http_response_group
+        groups http_after_response_group
+
+        scopes client_session_start
+        scopes frontend_tcp_request
+        scopes http_wait_request
+        scopes http_body_request
+        scopes frontend_http_request
+        scopes switching_rules_request
+        scopes backend_tcp_request
+        scopes backend_http_request
+        scopes process_server_rules_request
+        scopes http_process_request
+        scopes tcp_rdp_cookie_request
+        scopes process_sticking_rules_request
+        scopes client_session_end
+        scopes server_unavailable
+
+        scopes server_session_start
+        scopes tcp_response
+        scopes http_wait_response
+        scopes process_store_rules_response
+        scopes http_response http_response-error
+        scopes server_session_end
+
+    ot-group http_response_group
+        scopes http_response_1
+        scopes http_response_2
+
+    ot-scope http_response_1
+        span "HTTP response"
+            log "hdr.content" res.hdr("content-type") str("; length: ") res.hdr("content-length") str(" bytes")
+
+    ot-scope http_response_2
+        span "HTTP response"
+            log "hdr.date" res.hdr("date") str(" / ") res.hdr("last-modified")
+
+    ot-group http_after_response_group
+        scopes http_after_response
+
+    ot-scope http_after_response
+        span "HAProxy response" child-of "HAProxy session"
+            tag "error" bool(true)
+            tag "http.status_code" status
+
+    ot-scope client_session_start
+        span "HAProxy session" root
+            baggage "haproxy_id" var(sess.ot.uuid)
+        span "Client session" child-of "HAProxy session"
+        acl acl-test-src-ip src 127.0.0.1
+        event on-client-session-start if acl-test-src-ip
+
+    ot-scope frontend_tcp_request
+        span "Frontend TCP request" child-of "Client session"
+        event on-frontend-tcp-request
+
+    ot-scope http_wait_request
+        span "HTTP wait request" follows-from "Frontend TCP request"
+        finish "Frontend TCP request"
+        event on-http-wait-request
+
+    ot-scope http_body_request
+        span "HTTP body request" follows-from "HTTP wait request"
+        finish "HTTP wait request"
+        event on-http-body-request
+
+    ot-scope frontend_http_request
+        span "Frontend HTTP request" follows-from "HTTP body request"
+            tag "http.method" method
+            tag "http.url" url
+            tag "http.version" str("HTTP/") req.ver
+        finish "HTTP body request"
+        event on-frontend-http-request
+
+    ot-scope switching_rules_request
+        span "Switching rules request" follows-from "Frontend HTTP request"
+        finish "Frontend HTTP request"
+        event on-switching-rules-request
+
+    ot-scope backend_tcp_request
+        span "Backend TCP request" follows-from "Switching rules request"
+        finish "Switching rules request"
+        event on-backend-tcp-request
+
+    ot-scope backend_http_request
+        span "Backend HTTP request" follows-from "Backend TCP request"
+        finish "Backend TCP request"
+        event on-backend-http-request
+
+    ot-scope process_server_rules_request
+        span "Process server rules request" follows-from "Backend HTTP request"
+        finish "Backend HTTP request"
+        event on-process-server-rules-request
+
+    ot-scope http_process_request
+        span "HTTP process request" follows-from "Process server rules request"
+        finish "Process server rules request"
+        event on-http-process-request
+
+    ot-scope tcp_rdp_cookie_request
+        span "TCP RDP cookie request" follows-from "HTTP process request"
+        finish "HTTP process request"
+        event on-tcp-rdp-cookie-request
+
+    ot-scope process_sticking_rules_request
+        span "Process sticking rules request" follows-from "TCP RDP cookie request"
+        finish "TCP RDP cookie request"
+        event on-process-sticking-rules-request
+
+    ot-scope client_session_end
+        finish "Client session"
+        event on-client-session-end
+
+    ot-scope server_unavailable
+        finish *
+        event on-server-unavailable
+
+    ot-scope server_session_start
+        span "Server session" child-of "HAProxy session"
+        finish "Process sticking rules request"
+        event on-server-session-start
+
+    ot-scope tcp_response
+        span "TCP response" child-of "Server session"
+        event on-tcp-response
+
+    ot-scope http_wait_response
+        span "HTTP wait response" follows-from "TCP response"
+        finish "TCP response"
+        event on-http-wait-response
+
+    ot-scope process_store_rules_response
+        span "Process store rules response" follows-from "HTTP wait response"
+        finish "HTTP wait response"
+        event on-process-store-rules-response
+
+    ot-scope http_response
+        span "HTTP response" follows-from "Process store rules response"
+            tag "http.status_code" status
+        finish "Process store rules response"
+        event on-http-response
+
+    ot-scope http_response-error
+        span "HTTP response"
+            tag "error" bool(true)
+        event on-http-response if !acl-http-status-ok
+
+    ot-scope server_session_end
+        finish *
+        event on-server-session-end
diff --git a/addons/ot/test/test-speed.sh b/addons/ot/test/test-speed.sh
new file mode 100755
index 0000000..ef2ccf0
--- /dev/null
+++ b/addons/ot/test/test-speed.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+      _ARG_CFG="${1}"
+      _ARG_DIR="${2}"
+      _LOG_DIR="_logs"
+_HTTPD_PIDFILE="${_LOG_DIR}/thttpd.pid"
+
+
+httpd_run ()
+{
+
+	test -e "${_HTTPD_PIDFILE}" && return
+
+	thttpd -p 8000 -d . -nos -nov -l /dev/null -i "${_HTTPD_PIDFILE}"
+}
+
+httpd_stop ()
+{
+	test -e "${_HTTPD_PIDFILE}" || return
+
+	kill -TERM "$(cat ${_HTTPD_PIDFILE})"
+	rm "${_HTTPD_PIDFILE}"
+}
+
+haproxy_run ()
+{
+	_arg_ratio="${1}"
+	_var_sed_ot=
+	_var_sed_haproxy=
+
+	if test "${_arg_ratio}" = "disabled"; then
+		_var_sed_ot="s/no \(option disabled\)/\1/"
+	elif test "${_arg_ratio}" = "off"; then
+		_var_sed_haproxy="s/^\(.* filter opentracing .*\)/#\1/g; s/^\(.* ot-group .*\)/#\1/g"
+	else
+		_var_sed_ot="s/\(rate-limit\) 100.0/\1 ${_arg_ratio}/"
+	fi
+
+	sed "${_var_sed_haproxy}" "${_ARG_DIR}/haproxy.cfg.in" > "${_ARG_DIR}/haproxy.cfg"
+	sed "${_var_sed_ot}"      "${_ARG_DIR}/ot.cfg.in" > "${_ARG_DIR}/ot.cfg"
+
+	if test "${_ARG_DIR}" = "fe"; then
+		if test "${_arg_ratio}" = "disabled" -o "${_arg_ratio}" = "off"; then
+			sed "${_var_sed_haproxy}" "be/haproxy.cfg.in" > "be/haproxy.cfg"
+			sed "${_var_sed_ot}"      "be/ot.cfg.in" > "be/ot.cfg"
+		fi
+	fi
+
+	./run-${_ARG_CFG}.sh &
+	sleep 5
+}
+
+wrk_run ()
+{
+	_arg_ratio="${1}"
+
+	echo "--- rate-limit ${_arg_ratio} --------------------------------------------------"
+	wrk -c8 -d300 -t8 --latency http://localhost:10080/index.html
+	echo "----------------------------------------------------------------------"
+	echo
+
+	sleep 10
+}
+
+
+mkdir -p "${_LOG_DIR}" || exit 1
+
+if test "${_ARG_CFG}" = "all"; then
+	${0} fe-be fe > "${_LOG_DIR}/README-speed-fe-be"
+	${0} sa sa    > "${_LOG_DIR}/README-speed-sa"
+	${0} cmp cmp  > "${_LOG_DIR}/README-speed-cmp"
+	${0} ctx ctx  > "${_LOG_DIR}/README-speed-ctx"
+	exit 0
+fi
+
+test -n "${_ARG_CFG}" -a -f "run-${_ARG_CFG}.sh" || exit 2
+test -n "${_ARG_DIR}" -a -d "${_ARG_DIR}"        || exit 3
+
+test -e "${_ARG_DIR}/haproxy.cfg.in" || cp -af "${_ARG_DIR}/haproxy.cfg" "${_ARG_DIR}/haproxy.cfg.in"
+test -e "${_ARG_DIR}/ot.cfg.in"      || cp -af "${_ARG_DIR}/ot.cfg" "${_ARG_DIR}/ot.cfg.in"
+if test "${_ARG_DIR}" = "fe"; then
+	test -e "be/haproxy.cfg.in" || cp -af "be/haproxy.cfg" "be/haproxy.cfg.in"
+	test -e "be/ot.cfg.in"      || cp -af "be/ot.cfg" "be/ot.cfg.in"
+fi
+
+httpd_run
+
+for _var_ratio in 100.0 50.0 10.0 2.5 0.0 disabled off; do
+	haproxy_run "${_var_ratio}"
+	wrk_run "${_var_ratio}"
+
+	pkill --signal SIGUSR1 haproxy
+	wait
+done
+
+httpd_stop