summaryrefslogtreecommitdiffstats
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/51degrees/51d.c783
-rw-r--r--addons/51degrees/dummy/cityhash/city.c4
-rw-r--r--addons/51degrees/dummy/pattern/51Degrees.c114
-rw-r--r--addons/51degrees/dummy/pattern/51Degrees.h147
-rw-r--r--addons/51degrees/dummy/threading.c4
-rw-r--r--addons/51degrees/dummy/trie/51Degrees.c89
-rw-r--r--addons/51degrees/dummy/trie/51Degrees.h112
-rw-r--r--addons/deviceatlas/Makefile48
-rw-r--r--addons/deviceatlas/da.c501
-rw-r--r--addons/deviceatlas/dadwsch.c195
-rw-r--r--addons/deviceatlas/dummy/Makefile12
-rw-r--r--addons/deviceatlas/dummy/Os/daunix.c9
-rw-r--r--addons/deviceatlas/dummy/dac.c222
-rw-r--r--addons/deviceatlas/dummy/dac.h600
-rw-r--r--addons/deviceatlas/dummy/dadwcom.c1
-rw-r--r--addons/deviceatlas/dummy/dasch.c1
-rw-r--r--addons/deviceatlas/dummy/json.c1
-rw-r--r--addons/ot/AUTHORS1
-rw-r--r--addons/ot/MAINTAINERS1
-rw-r--r--addons/ot/Makefile75
-rw-r--r--addons/ot/README794
-rw-r--r--addons/ot/README-func298
-rw-r--r--addons/ot/README-pool25
-rw-r--r--addons/ot/include/cli.h50
-rw-r--r--addons/ot/include/conf.h228
-rw-r--r--addons/ot/include/config.h46
-rw-r--r--addons/ot/include/debug.h104
-rw-r--r--addons/ot/include/define.h107
-rw-r--r--addons/ot/include/event.h120
-rw-r--r--addons/ot/include/filter.h68
-rw-r--r--addons/ot/include/group.h61
-rw-r--r--addons/ot/include/http.h41
-rw-r--r--addons/ot/include/include.h66
-rw-r--r--addons/ot/include/opentracing.h86
-rw-r--r--addons/ot/include/parser.h172
-rw-r--r--addons/ot/include/pool.h39
-rw-r--r--addons/ot/include/scope.h126
-rw-r--r--addons/ot/include/util.h109
-rw-r--r--addons/ot/include/vars.h55
-rw-r--r--addons/ot/src/cli.c397
-rw-r--r--addons/ot/src/conf.c767
-rw-r--r--addons/ot/src/event.c338
-rw-r--r--addons/ot/src/filter.c1176
-rw-r--r--addons/ot/src/group.c354
-rw-r--r--addons/ot/src/http.c312
-rw-r--r--addons/ot/src/opentracing.c1067
-rw-r--r--addons/ot/src/parser.c1225
-rw-r--r--addons/ot/src/pool.c223
-rw-r--r--addons/ot/src/scope.c634
-rw-r--r--addons/ot/src/util.c815
-rw-r--r--addons/ot/src/vars.c834
-rw-r--r--addons/ot/test/README-speed-cmp111
-rw-r--r--addons/ot/test/README-speed-ctx111
-rw-r--r--addons/ot/test/README-speed-fe-be111
-rw-r--r--addons/ot/test/README-speed-sa111
-rw-r--r--addons/ot/test/be/cfg-dd.json5
-rw-r--r--addons/ot/test/be/cfg-jaeger.yml34
-rw-r--r--addons/ot/test/be/cfg-zipkin.json4
-rw-r--r--addons/ot/test/be/haproxy.cfg37
-rw-r--r--addons/ot/test/be/ot.cfg62
-rw-r--r--addons/ot/test/cmp/cfg-dd.json5
-rw-r--r--addons/ot/test/cmp/cfg-jaeger.yml34
-rw-r--r--addons/ot/test/cmp/cfg-zipkin.json4
-rw-r--r--addons/ot/test/cmp/haproxy.cfg36
-rw-r--r--addons/ot/test/cmp/ot.cfg83
-rw-r--r--addons/ot/test/ctx/cfg-dd.json5
-rw-r--r--addons/ot/test/ctx/cfg-jaeger.yml34
-rw-r--r--addons/ot/test/ctx/cfg-zipkin.json4
-rw-r--r--addons/ot/test/ctx/haproxy.cfg38
-rw-r--r--addons/ot/test/ctx/ot.cfg197
-rw-r--r--addons/ot/test/empty/cfg-dd.json5
-rw-r--r--addons/ot/test/empty/cfg-jaeger.yml34
-rw-r--r--addons/ot/test/empty/cfg-zipkin.json4
-rw-r--r--addons/ot/test/empty/haproxy.cfg30
-rw-r--r--addons/ot/test/empty/ot.cfg3
-rw-r--r--addons/ot/test/fe/cfg-dd.json5
-rw-r--r--addons/ot/test/fe/cfg-jaeger.yml34
-rw-r--r--addons/ot/test/fe/cfg-zipkin.json4
-rw-r--r--addons/ot/test/fe/haproxy.cfg37
-rw-r--r--addons/ot/test/fe/ot.cfg74
-rwxr-xr-xaddons/ot/test/func-stat.sh5
-rwxr-xr-xaddons/ot/test/get-opentracing-plugins.sh45
-rw-r--r--addons/ot/test/index.html1
-rwxr-xr-xaddons/ot/test/run-cmp.sh13
-rwxr-xr-xaddons/ot/test/run-ctx.sh13
-rwxr-xr-xaddons/ot/test/run-fe-be.sh47
-rwxr-xr-xaddons/ot/test/run-sa.sh13
-rw-r--r--addons/ot/test/sa/cfg-dd.json5
-rw-r--r--addons/ot/test/sa/cfg-jaeger.yml34
-rw-r--r--addons/ot/test/sa/cfg-zipkin.json4
-rw-r--r--addons/ot/test/sa/haproxy.cfg40
-rw-r--r--addons/ot/test/sa/ot.cfg160
-rwxr-xr-xaddons/ot/test/test-speed.sh117
-rw-r--r--addons/promex/README356
-rw-r--r--addons/promex/service-prometheus.c1659
-rw-r--r--addons/wurfl/dummy/Makefile13
-rw-r--r--addons/wurfl/dummy/dummy-wurfl.c126
-rw-r--r--addons/wurfl/dummy/wurfl/wurfl.h409
-rw-r--r--addons/wurfl/wurfl.c779
99 files changed, 18752 insertions, 0 deletions
diff --git a/addons/51degrees/51d.c b/addons/51degrees/51d.c
new file mode 100644
index 0000000..5d68695
--- /dev/null
+++ b/addons/51degrees/51d.c
@@ -0,0 +1,783 @@
+#include <stdio.h>
+
+#include <import/lru.h>
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/buf-t.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/chunk.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/http_ana.h>
+#include <haproxy/http_fetch.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/sample.h>
+#include <haproxy/thread.h>
+#include <haproxy/tools.h>
+#include <haproxy/xxhash.h>
+#include <51Degrees.h>
+
+struct _51d_property_names {
+ struct list list;
+ char *name;
+};
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+static struct lru64_head *_51d_lru_tree = NULL;
+static unsigned long long _51d_lru_seed;
+
+__decl_spinlock(_51d_lru_lock);
+#endif
+
+static struct {
+ char property_separator; /* the separator to use in the response for the values. this is taken from 51degrees-property-separator from config. */
+ struct list property_names; /* list of properties to load into the data set. this is taken from 51degrees-property-name-list from config. */
+ char *data_file_path;
+ int header_count; /* number of HTTP headers related to device detection. */
+ struct buffer *header_names; /* array of HTTP header names. */
+ fiftyoneDegreesDataSet data_set; /* data set used with the pattern and trie detection methods. */
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ fiftyoneDegreesWorksetPool *pool; /* pool of worksets to avoid creating a new one for each request. */
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+ int32_t *header_offsets; /* offsets to the HTTP header name string. */
+#ifdef FIFTYONEDEGREES_NO_THREADING
+ fiftyoneDegreesDeviceOffsets device_offsets; /* Memory used for device offsets. */
+#endif
+#endif
+ int cache_size;
+} global_51degrees = {
+ .property_separator = ',',
+ .property_names = LIST_HEAD_INIT(global_51degrees.property_names),
+ .data_file_path = NULL,
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ .data_set = { },
+#endif
+ .cache_size = 0,
+};
+
+static int _51d_data_file(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err,
+ "'%s' expects a filepath to a 51Degrees trie or pattern data file.",
+ args[0]);
+ return -1;
+ }
+
+ if (global_51degrees.data_file_path)
+ free(global_51degrees.data_file_path);
+ global_51degrees.data_file_path = strdup(args[1]);
+
+ return 0;
+}
+
+static int _51d_property_name_list(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int cur_arg = 1;
+ struct _51d_property_names *name;
+
+ if (*(args[cur_arg]) == 0) {
+ memprintf(err,
+ "'%s' expects at least one 51Degrees property name.",
+ args[0]);
+ return -1;
+ }
+
+ while (*(args[cur_arg])) {
+ name = calloc(1, sizeof(*name));
+ name->name = strdup(args[cur_arg]);
+ LIST_APPEND(&global_51degrees.property_names, &name->list);
+ ++cur_arg;
+ }
+
+ return 0;
+}
+
+static int _51d_property_separator(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err,
+ "'%s' expects a single character.",
+ args[0]);
+ return -1;
+ }
+ if (strlen(args[1]) > 1) {
+ memprintf(err,
+ "'%s' expects a single character, got '%s'.",
+ args[0], args[1]);
+ return -1;
+ }
+
+ global_51degrees.property_separator = *args[1];
+
+ return 0;
+}
+
+static int _51d_cache_size(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err,
+ "'%s' expects a positive numeric value.",
+ args[0]);
+ return -1;
+ }
+
+ global_51degrees.cache_size = atoi(args[1]);
+ if (global_51degrees.cache_size < 0) {
+ memprintf(err,
+ "'%s' expects a positive numeric value, got '%s'.",
+ args[0], args[1]);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _51d_fetch_check(struct arg *arg, char **err_msg)
+{
+ if (global_51degrees.data_file_path)
+ return 1;
+
+ memprintf(err_msg, "51Degrees data file is not specified (parameter '51degrees-data-file')");
+ return 0;
+}
+
+static int _51d_conv_check(struct arg *arg, struct sample_conv *conv,
+ const char *file, int line, char **err_msg)
+{
+ if (global_51degrees.data_file_path)
+ return 1;
+
+ memprintf(err_msg, "51Degrees data file is not specified (parameter '51degrees-data-file')");
+ return 0;
+}
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+static void _51d_lru_free(void *cache_entry)
+{
+ struct buffer *ptr = cache_entry;
+
+ if (!ptr)
+ return;
+
+ free(ptr->area);
+ free(ptr);
+}
+
+/* Allocates memory freeing space in the cache if necessary.
+*/
+static void *_51d_malloc(int size)
+{
+ void *ptr = malloc(size);
+
+ if (!ptr) {
+ /* free the oldest 10 entries from lru to free up some memory
+ * then try allocating memory again */
+ lru64_kill_oldest(_51d_lru_tree, 10);
+ ptr = malloc(size);
+ }
+
+ return ptr;
+}
+
+/* Insert the data associated with the sample into the cache as a fresh item.
+ */
+static void _51d_insert_cache_entry(struct sample *smp, struct lru64 *lru, void* domain)
+{
+ struct buffer *cache_entry = _51d_malloc(sizeof(*cache_entry));
+
+ if (!cache_entry)
+ return;
+
+ cache_entry->area = _51d_malloc(smp->data.u.str.data + 1);
+ if (!cache_entry->area) {
+ free(cache_entry);
+ return;
+ }
+
+ memcpy(cache_entry->area, smp->data.u.str.area, smp->data.u.str.data);
+ cache_entry->area[smp->data.u.str.data] = 0;
+ cache_entry->data = smp->data.u.str.data;
+ HA_SPIN_LOCK(OTHER_LOCK, &_51d_lru_lock);
+ lru64_commit(lru, cache_entry, domain, 0, _51d_lru_free);
+ HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
+}
+
+/* Retrieves the data from the cache and sets the sample data to this string.
+ */
+static void _51d_retrieve_cache_entry(struct sample *smp, struct lru64 *lru)
+{
+ struct buffer *cache_entry = lru->data;
+ smp->data.u.str.area = cache_entry->area;
+ smp->data.u.str.data = cache_entry->data;
+}
+#endif
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+/* Sets the important HTTP headers ahead of the detection
+ */
+static void _51d_set_headers(struct sample *smp, fiftyoneDegreesWorkset *ws)
+{
+ struct channel *chn;
+ struct htx *htx;
+ struct http_hdr_ctx ctx;
+ struct ist name;
+ int i;
+
+ ws->importantHeadersCount = 0;
+ chn = (smp->strm ? &smp->strm->req : NULL);
+
+ // No need to null check as this has already been carried out in the
+ // calling method
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ ALREADY_CHECKED(htx);
+
+ for (i = 0; i < global_51degrees.header_count; i++) {
+ name = ist2((global_51degrees.header_names + i)->area,
+ (global_51degrees.header_names + i)->data);
+ ctx.blk = NULL;
+
+ if (http_find_header(htx, name, &ctx, 1)) {
+ ws->importantHeaders[ws->importantHeadersCount].header = ws->dataSet->httpHeaders + i;
+ ws->importantHeaders[ws->importantHeadersCount].headerValue = ctx.value.ptr;
+ ws->importantHeaders[ws->importantHeadersCount].headerValueLength = ctx.value.len;
+ ws->importantHeadersCount++;
+ }
+ }
+}
+#endif
+
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+static void _51d_init_device_offsets(fiftyoneDegreesDeviceOffsets *offsets) {
+ int i;
+ for (i = 0; i < global_51degrees.data_set.uniqueHttpHeaders.count; i++) {
+ offsets->firstOffset[i].userAgent = NULL;
+ }
+}
+
+static void _51d_set_device_offsets(struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets)
+{
+ struct channel *chn;
+ struct htx *htx;
+ struct http_hdr_ctx ctx;
+ struct ist name;
+ int i;
+
+ offsets->size = 0;
+ chn = (smp->strm ? &smp->strm->req : NULL);
+
+ // No need to null check as this has already been carried out in the
+ // calling method
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ ALREADY_CHECKED(htx);
+
+ for (i = 0; i < global_51degrees.header_count; i++) {
+ name = ist2((global_51degrees.header_names + i)->area,
+ (global_51degrees.header_names + i)->data);
+ ctx.blk = NULL;
+
+ if (http_find_header(htx, name, &ctx, 1)) {
+ (offsets->firstOffset + offsets->size)->httpHeaderOffset = *(global_51degrees.header_offsets + i);
+ (offsets->firstOffset + offsets->size)->deviceOffset = fiftyoneDegreesGetDeviceOffset(&global_51degrees.data_set, ctx.value.ptr);
+ offsets->size++;
+ }
+ }
+
+}
+#endif
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+/* Provides a hash code for the important HTTP headers.
+ */
+unsigned long long _51d_req_hash(const struct arg *args, fiftyoneDegreesWorkset* ws)
+{
+ unsigned long long seed = _51d_lru_seed ^ (long)args;
+ unsigned long long hash = 0;
+ int i;
+ for(i = 0; i < ws->importantHeadersCount; i++) {
+ hash ^= ws->importantHeaders[i].header->headerNameOffset;
+ hash ^= XXH3(ws->importantHeaders[i].headerValue,
+ ws->importantHeaders[i].headerValueLength,
+ seed);
+ }
+ return hash;
+}
+#endif
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+static void _51d_process_match(const struct arg *args, struct sample *smp, fiftyoneDegreesWorkset* ws)
+{
+ char *methodName;
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+static void _51d_process_match(const struct arg *args, struct sample *smp, fiftyoneDegreesDeviceOffsets *offsets)
+{
+ char valuesBuffer[1024];
+ const char **requiredProperties = fiftyoneDegreesGetRequiredPropertiesNames(&global_51degrees.data_set);
+ int requiredPropertiesCount = fiftyoneDegreesGetRequiredPropertiesCount(&global_51degrees.data_set);
+#endif
+
+ char no_data[] = "NoData"; /* response when no data could be found */
+ struct buffer *temp = get_trash_chunk();
+ int j, i = 0, found;
+ const char* property_name;
+
+ /* Loop through property names passed to the filter and fetch them from the dataset. */
+ while (args[i].data.str.area) {
+ /* Try to find request property in dataset. */
+ found = 0;
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ if (strcmp("Method", args[i].data.str.area) == 0) {
+ switch(ws->method) {
+ case EXACT: methodName = "Exact"; break;
+ case NUMERIC: methodName = "Numeric"; break;
+ case NEAREST: methodName = "Nearest"; break;
+ case CLOSEST: methodName = "Closest"; break;
+ default:
+ case NONE: methodName = "None"; break;
+ }
+ chunk_appendf(temp, "%s", methodName);
+ found = 1;
+ }
+ else if (strcmp("Difference", args[i].data.str.area) == 0) {
+ chunk_appendf(temp, "%d", ws->difference);
+ found = 1;
+ }
+ else if (strcmp("Rank", args[i].data.str.area) == 0) {
+ chunk_appendf(temp, "%d", fiftyoneDegreesGetSignatureRank(ws));
+ found = 1;
+ }
+ else {
+ for (j = 0; j < ws->dataSet->requiredPropertyCount; j++) {
+ property_name = fiftyoneDegreesGetPropertyName(ws->dataSet, ws->dataSet->requiredProperties[j]);
+ if (strcmp(property_name, args[i].data.str.area) == 0) {
+ found = 1;
+ fiftyoneDegreesSetValues(ws, j);
+ chunk_appendf(temp, "%s", fiftyoneDegreesGetValueName(ws->dataSet, *ws->values));
+ break;
+ }
+ }
+ }
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+ found = 0;
+ for (j = 0; j < requiredPropertiesCount; j++) {
+ property_name = requiredProperties[j];
+ if (strcmp(property_name, args[i].data.str.area) == 0 &&
+ fiftyoneDegreesGetValueFromOffsets(&global_51degrees.data_set, offsets, j, valuesBuffer, 1024) > 0) {
+ found = 1;
+ chunk_appendf(temp, "%s", valuesBuffer);
+ break;
+ }
+ }
+#endif
+ if (!found)
+ chunk_appendf(temp, "%s", no_data);
+
+ /* Add separator. */
+ chunk_appendf(temp, "%c", global_51degrees.property_separator);
+ ++i;
+ }
+
+ if (temp->data) {
+ --temp->data;
+ temp->area[temp->data] = '\0';
+ }
+
+ smp->data.u.str.area = temp->area;
+ smp->data.u.str.data = temp->data;
+}
+
+/* Sets the sample data as a constant string. This ensures that the
+ * string will be processed correctly.
+ */
+static void _51d_set_smp(struct sample *smp)
+{
+ /*
+ * Data type has to be set to ensure the string output is processed
+ * correctly.
+ */
+ smp->data.type = SMP_T_STR;
+
+ /* Flags the sample to show it uses constant memory. */
+ smp->flags |= SMP_F_CONST;
+}
+
+static int _51d_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ fiftyoneDegreesWorkset* ws; /* workset for detection */
+ struct lru64 *lru = NULL;
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+ fiftyoneDegreesDeviceOffsets *offsets; /* Offsets for detection */
+
+#endif
+ struct channel *chn;
+ struct htx *htx;
+
+ chn = (smp->strm ? &smp->strm->req : NULL);
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ if (!htx)
+ return 0;
+
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+
+ /* Get only the headers needed for device detection so they can be used
+ * with the cache to return previous results. Pattern is slower than
+ * Trie so caching will help improve performance.
+ */
+
+ /* Get a workset from the pool which will later contain detection results. */
+ ws = fiftyoneDegreesWorksetPoolGet(global_51degrees.pool);
+ if (!ws)
+ return 0;
+
+ /* Set the important HTTP headers for this request in the workset. */
+ _51d_set_headers(smp, ws);
+
+ /* Check the cache to see if there's results for these headers already. */
+ if (_51d_lru_tree) {
+ HA_SPIN_LOCK(OTHER_LOCK, &_51d_lru_lock);
+
+ lru = lru64_get(_51d_req_hash(args, ws),
+ _51d_lru_tree, (void*)args, 0);
+
+ if (lru && lru->domain) {
+ fiftyoneDegreesWorksetPoolRelease(global_51degrees.pool, ws);
+ _51d_retrieve_cache_entry(smp, lru);
+ HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
+
+ _51d_set_smp(smp);
+ return 1;
+ }
+ HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
+ }
+
+ fiftyoneDegreesMatchForHttpHeaders(ws);
+
+ _51d_process_match(args, smp, ws);
+
+#endif
+
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifndef FIFTYONEDEGREES_NO_THREADING
+ offsets = fiftyoneDegreesCreateDeviceOffsets(&global_51degrees.data_set);
+ _51d_init_device_offsets(offsets);
+#else
+ offsets = &global_51degrees.device_offsets;
+#endif
+
+ /* Trie is very fast so all the headers can be passed in and the result
+ * returned faster than the hashing algorithm process.
+ */
+ _51d_set_device_offsets(smp, offsets);
+ _51d_process_match(args, smp, offsets);
+
+#ifndef FIFTYONEDEGREES_NO_THREADING
+ fiftyoneDegreesFreeDeviceOffsets(offsets);
+#endif
+
+#endif
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ fiftyoneDegreesWorksetPoolRelease(global_51degrees.pool, ws);
+ if (lru)
+ _51d_insert_cache_entry(smp, lru, (void*)args);
+#endif
+
+ _51d_set_smp(smp);
+ return 1;
+}
+
+static int _51d_conv(const struct arg *args, struct sample *smp, void *private)
+{
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ fiftyoneDegreesWorkset* ws; /* workset for detection */
+ struct lru64 *lru = NULL;
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+ fiftyoneDegreesDeviceOffsets *offsets; /* Offsets for detection */
+#endif
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+
+ /* Look in the list. */
+ if (_51d_lru_tree) {
+ unsigned long long seed = _51d_lru_seed ^ (long)args;
+
+ HA_SPIN_LOCK(OTHER_LOCK, &_51d_lru_lock);
+ lru = lru64_get(XXH3(smp->data.u.str.area, smp->data.u.str.data, seed),
+ _51d_lru_tree, (void*)args, 0);
+ if (lru && lru->domain) {
+ _51d_retrieve_cache_entry(smp, lru);
+ HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
+ return 1;
+ }
+ HA_SPIN_UNLOCK(OTHER_LOCK, &_51d_lru_lock);
+ }
+
+ /* Create workset. This will later contain detection results. */
+ ws = fiftyoneDegreesWorksetPoolGet(global_51degrees.pool);
+ if (!ws)
+ return 0;
+#endif
+
+ /* Duplicate the data and remove the "const" flag before device detection. */
+ if (!smp_dup(smp))
+ return 0;
+
+ smp->data.u.str.area[smp->data.u.str.data] = '\0';
+
+ /* Perform detection. */
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ fiftyoneDegreesMatch(ws, smp->data.u.str.area);
+ _51d_process_match(args, smp, ws);
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifndef FIFTYONEDEGREES_NO_THREADING
+ offsets = fiftyoneDegreesCreateDeviceOffsets(&global_51degrees.data_set);
+ _51d_init_device_offsets(offsets);
+#else
+ offsets = &global_51degrees.device_offsets;
+#endif
+
+ offsets->firstOffset->deviceOffset = fiftyoneDegreesGetDeviceOffset(&global_51degrees.data_set,
+ smp->data.u.str.area);
+ offsets->size = 1;
+ _51d_process_match(args, smp, offsets);
+#endif
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ fiftyoneDegreesWorksetPoolRelease(global_51degrees.pool, ws);
+ if (lru)
+ _51d_insert_cache_entry(smp, lru, (void*)args);
+#endif
+
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifndef FIFTYONEDEGREES_NO_THREADING
+ fiftyoneDegreesFreeDeviceOffsets(offsets);
+#endif
+#endif
+
+ _51d_set_smp(smp);
+ return 1;
+}
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+void _51d_init_http_headers()
+{
+ int index = 0;
+ const fiftyoneDegreesAsciiString *headerName;
+ fiftyoneDegreesDataSet *ds = &global_51degrees.data_set;
+ global_51degrees.header_count = ds->httpHeadersCount;
+ global_51degrees.header_names = malloc(global_51degrees.header_count * sizeof(struct buffer));
+ for (index = 0; index < global_51degrees.header_count; index++) {
+ headerName = fiftyoneDegreesGetString(ds, ds->httpHeaders[index].headerNameOffset);
+ (global_51degrees.header_names + index)->area = (char*)&headerName->firstByte;
+ (global_51degrees.header_names + index)->data = headerName->length - 1;
+ (global_51degrees.header_names + index)->size = (global_51degrees.header_names + index)->data;
+ }
+}
+#endif
+
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+void _51d_init_http_headers()
+{
+ int index = 0;
+ fiftyoneDegreesDataSet *ds = &global_51degrees.data_set;
+ global_51degrees.header_count = fiftyoneDegreesGetHttpHeaderCount(ds);
+#ifdef FIFTYONEDEGREES_NO_THREADING
+ global_51degrees.device_offsets.firstOffset = malloc(
+ global_51degrees.header_count * sizeof(fiftyoneDegreesDeviceOffset));
+ _51d_init_device_offsets(&global_51degrees.device_offsets);
+#endif
+ global_51degrees.header_names = malloc(global_51degrees.header_count * sizeof(struct buffer));
+ global_51degrees.header_offsets = malloc(global_51degrees.header_count * sizeof(int32_t));
+ for (index = 0; index < global_51degrees.header_count; index++) {
+ global_51degrees.header_offsets[index] = fiftyoneDegreesGetHttpHeaderNameOffset(ds, index);
+ global_51degrees.header_names[index].area = (char*)fiftyoneDegreesGetHttpHeaderNamePointer(ds, index);
+ global_51degrees.header_names[index].data = strlen(global_51degrees.header_names[index].area);
+ global_51degrees.header_names[index].size = global_51degrees.header_names->data;
+ }
+}
+#endif
+
+/*
+ * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
+ */
+static int init_51degrees(void)
+{
+ int i = 0;
+ struct buffer *temp;
+ struct _51d_property_names *name;
+ char **_51d_property_list = NULL;
+ fiftyoneDegreesDataSetInitStatus _51d_dataset_status = DATA_SET_INIT_STATUS_NOT_SET;
+
+ if (!global_51degrees.data_file_path)
+ return ERR_NONE;
+
+ if (global.nbthread < 1) {
+ ha_alert("51Degrees: The thread count cannot be zero or negative.\n");
+ return (ERR_FATAL | ERR_ALERT);
+ }
+
+ if (!LIST_ISEMPTY(&global_51degrees.property_names)) {
+ i = 0;
+ list_for_each_entry(name, &global_51degrees.property_names, list)
+ ++i;
+ _51d_property_list = calloc(i, sizeof(*_51d_property_list));
+
+ i = 0;
+ list_for_each_entry(name, &global_51degrees.property_names, list)
+ _51d_property_list[i++] = name->name;
+ }
+
+ _51d_dataset_status = fiftyoneDegreesInitWithPropertyArray(global_51degrees.data_file_path, &global_51degrees.data_set, (const char**)_51d_property_list, i);
+
+ temp = get_trash_chunk();
+ chunk_reset(temp);
+
+ switch (_51d_dataset_status) {
+ case DATA_SET_INIT_STATUS_SUCCESS:
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ global_51degrees.pool = fiftyoneDegreesWorksetPoolCreate(&global_51degrees.data_set, NULL, global.nbthread);
+#endif
+ _51d_init_http_headers();
+ break;
+ case DATA_SET_INIT_STATUS_INSUFFICIENT_MEMORY:
+ chunk_printf(temp, "Insufficient memory.");
+ break;
+ case DATA_SET_INIT_STATUS_CORRUPT_DATA:
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ chunk_printf(temp, "Corrupt data file. Check that the data file provided is uncompressed and Pattern data format.");
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+ chunk_printf(temp, "Corrupt data file. Check that the data file provided is uncompressed and Trie data format.");
+#endif
+ break;
+ case DATA_SET_INIT_STATUS_INCORRECT_VERSION:
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ chunk_printf(temp, "Incorrect version. Check that the data file provided is uncompressed and Pattern data format.");
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+ chunk_printf(temp, "Incorrect version. Check that the data file provided is uncompressed and Trie data format.");
+#endif
+ break;
+ case DATA_SET_INIT_STATUS_FILE_NOT_FOUND:
+ chunk_printf(temp, "File not found.");
+ break;
+ case DATA_SET_INIT_STATUS_NULL_POINTER:
+ chunk_printf(temp, "Null pointer to the existing dataset or memory location.");
+ break;
+ case DATA_SET_INIT_STATUS_POINTER_OUT_OF_BOUNDS:
+ chunk_printf(temp, "Allocated continuous memory containing 51Degrees data file appears to be smaller than expected. Most likely"
+ " because the data file was not fully loaded into the allocated memory.");
+ break;
+ case DATA_SET_INIT_STATUS_NOT_SET:
+ chunk_printf(temp, "Data set not initialised.");
+ break;
+ default:
+ chunk_printf(temp, "Other error.");
+ break;
+ }
+ if (_51d_dataset_status != DATA_SET_INIT_STATUS_SUCCESS) {
+ if (temp->data)
+ ha_alert("51Degrees Setup - Error reading 51Degrees data file. %s\n",
+ temp->area);
+ else
+ ha_alert("51Degrees Setup - Error reading 51Degrees data file.\n");
+ return ERR_ALERT | ERR_FATAL;
+ }
+ free(_51d_property_list);
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ _51d_lru_seed = ha_random();
+ if (global_51degrees.cache_size) {
+ _51d_lru_tree = lru64_new(global_51degrees.cache_size);
+ }
+#endif
+
+ return ERR_NONE;
+}
+
+static void deinit_51degrees(void)
+{
+ struct _51d_property_names *_51d_prop_name, *_51d_prop_nameb;
+
+ free(global_51degrees.header_names);
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ if (global_51degrees.pool)
+ fiftyoneDegreesWorksetPoolFree(global_51degrees.pool);
+#endif
+#ifdef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#ifdef FIFTYONEDEGREES_NO_THREADING
+ free(global_51degrees.device_offsets.firstOffset);
+#endif
+ free(global_51degrees.header_offsets);
+#endif
+ fiftyoneDegreesDataSetFree(&global_51degrees.data_set);
+
+ ha_free(&global_51degrees.data_file_path);
+ list_for_each_entry_safe(_51d_prop_name, _51d_prop_nameb, &global_51degrees.property_names, list) {
+ LIST_DELETE(&_51d_prop_name->list);
+ free(_51d_prop_name);
+ }
+
+#ifdef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+ while (lru64_destroy(_51d_lru_tree));
+#endif
+}
+
+static struct cfg_kw_list _51dcfg_kws = {{ }, {
+ { CFG_GLOBAL, "51degrees-data-file", _51d_data_file },
+ { CFG_GLOBAL, "51degrees-property-name-list", _51d_property_name_list },
+ { CFG_GLOBAL, "51degrees-property-separator", _51d_property_separator },
+ { CFG_GLOBAL, "51degrees-cache-size", _51d_cache_size },
+ { 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &_51dcfg_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
+ { "51d.all", _51d_fetch, ARG5(1,STR,STR,STR,STR,STR), _51d_fetch_check, SMP_T_STR, SMP_USE_HRQHV },
+ { NULL, NULL, 0, 0, 0 },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_conv_kw_list conv_kws = {ILH, {
+ { "51d.single", _51d_conv, ARG5(1,STR,STR,STR,STR,STR), _51d_conv_check, SMP_T_STR, SMP_T_STR },
+ { NULL, NULL, 0, 0, 0 },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_convs, &conv_kws);
+
+REGISTER_POST_CHECK(init_51degrees);
+REGISTER_POST_DEINIT(deinit_51degrees);
+
+#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED)
+#ifndef FIFTYONEDEGREES_DUMMY_LIB
+ REGISTER_BUILD_OPTS("Built with 51Degrees Pattern support.");
+#else
+ REGISTER_BUILD_OPTS("Built with 51Degrees Pattern support (dummy library).");
+#endif
+#elif defined(FIFTYONEDEGREES_H_TRIE_INCLUDED)
+#ifndef FIFTYONEDEGREES_DUMMY_LIB
+ REGISTER_BUILD_OPTS("Built with 51Degrees Trie support.");
+#else
+ REGISTER_BUILD_OPTS("Built with 51Degrees Trie support (dummy library).");
+#endif
+#endif
diff --git a/addons/51degrees/dummy/cityhash/city.c b/addons/51degrees/dummy/cityhash/city.c
new file mode 100644
index 0000000..b6b08bf
--- /dev/null
+++ b/addons/51degrees/dummy/cityhash/city.c
@@ -0,0 +1,4 @@
+typedef struct cityhash_t {
+ // This is an empty structure to ensure a city.o is generated
+ // by the dummy library, it is never used.
+} dummyCityHash; \ No newline at end of file
diff --git a/addons/51degrees/dummy/pattern/51Degrees.c b/addons/51degrees/dummy/pattern/51Degrees.c
new file mode 100644
index 0000000..c002e5c
--- /dev/null
+++ b/addons/51degrees/dummy/pattern/51Degrees.c
@@ -0,0 +1,114 @@
+/* *********************************************************************
+ * This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
+ * Copyright 2019 51Degrees Mobile Experts Limited, 5 Charlotte Close,
+ * Caversham, Reading, Berkshire, United Kingdom RG4 7BY
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.
+ *
+ * If a copy of the MPL was not distributed with this file, You can obtain
+ * one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0.
+ * *********************************************************************/
+
+/* *********************************************************************
+ * Dummy library for HAProxy. This does not function, and is designed
+ * solely for HAProxy testing purposes.
+ * *********************************************************************/
+#include "51Degrees.h"
+#include <stdlib.h>
+
+int32_t fiftyoneDegreesGetSignatureRank(fiftyoneDegreesWorkset *ws) {
+ return 0;
+}
+
+const char* fiftyoneDegreesGetPropertyName(
+ const fiftyoneDegreesDataSet *dataSet,
+ const fiftyoneDegreesProperty *property) {
+ return "dummy-property";
+}
+
+int32_t fiftyoneDegreesSetValues(
+ fiftyoneDegreesWorkset *ws,
+ int32_t requiredPropertyIndex) {
+ return 0;
+}
+
+const char* fiftyoneDegreesGetValueName(
+ const fiftyoneDegreesDataSet *dataSet,
+ const fiftyoneDegreesValue *value) {
+ return "dummy-value";
+}
+
+static fiftyoneDegreesDataSet dummyDataSet = {
+ 0,
+ (fiftyoneDegreesHttpHeader*)NULL,
+ 0,
+ (const fiftyoneDegreesProperty**)NULL
+};
+
+static fiftyoneDegreesWorkset dummyWorkset = {
+ &dummyDataSet,
+ 0,
+ (fiftyoneDegreesHttpHeaderWorkset*)NULL,
+ EXACT,
+ 0,
+ (const fiftyoneDegreesValue **)NULL
+};
+
+fiftyoneDegreesWorkset *fiftyoneDegreesWorksetPoolGet(
+ fiftyoneDegreesWorksetPool *pool) {
+ return &dummyWorkset;
+}
+
+void fiftyoneDegreesWorksetPoolRelease(
+ fiftyoneDegreesWorksetPool *pool,
+ fiftyoneDegreesWorkset *ws) {
+ return;
+}
+
+void fiftyoneDegreesMatchForHttpHeaders(fiftyoneDegreesWorkset *ws) {
+ return;
+}
+
+void fiftyoneDegreesMatch(
+ fiftyoneDegreesWorkset *ws,
+ const char* userAgent) {
+ return;
+}
+
+fiftyoneDegreesDataSetInitStatus fiftyoneDegreesInitWithPropertyArray(
+ const char *fileName,
+ fiftyoneDegreesDataSet *dataSet,
+ const char** properties,
+ int32_t count) {
+ return DATA_SET_INIT_STATUS_SUCCESS;
+}
+
+static fiftyoneDegreesWorksetPool dummyWorksetPool;
+
+fiftyoneDegreesWorksetPool *fiftyoneDegreesWorksetPoolCreate(
+ fiftyoneDegreesDataSet *dataSet,
+ fiftyoneDegreesResultsetCache *cache,
+ int32_t size) {
+ return &dummyWorksetPool;
+}
+
+void fiftyoneDegreesWorksetPoolFree(
+ const fiftyoneDegreesWorksetPool *pool) {
+ return;
+}
+
+void fiftyoneDegreesDataSetFree(const fiftyoneDegreesDataSet *dataSet) {
+ return;
+}
+
+static fiftyoneDegreesAsciiString dummyAsciiString = {0, 0};
+
+const fiftyoneDegreesAsciiString* fiftyoneDegreesGetString(
+ const fiftyoneDegreesDataSet *dataSet,
+ int32_t offset) {
+ return &dummyAsciiString;
+} \ No newline at end of file
diff --git a/addons/51degrees/dummy/pattern/51Degrees.h b/addons/51degrees/dummy/pattern/51Degrees.h
new file mode 100644
index 0000000..9aaf949
--- /dev/null
+++ b/addons/51degrees/dummy/pattern/51Degrees.h
@@ -0,0 +1,147 @@
+/* *********************************************************************
+ * This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
+ * Copyright 2019 51Degrees Mobile Experts Limited, 5 Charlotte Close,
+ * Caversham, Reading, Berkshire, United Kingdom RG4 7BY
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.
+ *
+ * If a copy of the MPL was not distributed with this file, You can obtain
+ * one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0.
+ * *********************************************************************/
+
+/* *********************************************************************
+ * Dummy library for HAProxy. This does not function, and is designed
+ * solely for HAProxy testing purposes.
+ * *********************************************************************/
+#ifndef FIFTYONEDEGREES_H_INCLUDED
+#define FIFTYONEDEGREES_H_INCLUDED
+
+#ifndef FIFTYONEDEGREES_H_PATTERN_INCLUDED
+#define FIFTYONEDEGREES_H_PATTERN_INCLUDED
+#endif
+
+#ifndef FIFTYONEDEGREES_DUMMY_LIB
+#define FIFTYONEDEGREES_DUMMY_LIB
+#endif
+
+#include <stdint.h>
+
+typedef enum e_fiftyoneDegrees_MatchMethod {
+ NONE,
+ EXACT,
+ NUMERIC,
+ NEAREST,
+ CLOSEST
+} fiftyoneDegreesMatchMethod;
+
+typedef enum e_fiftyoneDegrees_DataSetInitStatus {
+ DATA_SET_INIT_STATUS_SUCCESS,
+ DATA_SET_INIT_STATUS_INSUFFICIENT_MEMORY,
+ DATA_SET_INIT_STATUS_CORRUPT_DATA,
+ DATA_SET_INIT_STATUS_INCORRECT_VERSION,
+ DATA_SET_INIT_STATUS_FILE_NOT_FOUND,
+ DATA_SET_INIT_STATUS_NOT_SET,
+ DATA_SET_INIT_STATUS_POINTER_OUT_OF_BOUNDS,
+ DATA_SET_INIT_STATUS_NULL_POINTER
+} fiftyoneDegreesDataSetInitStatus;
+
+typedef struct fiftyoneDegrees_ascii_string_t {
+ const int16_t length;
+ const char firstByte;
+} fiftyoneDegreesAsciiString;
+
+typedef struct fiftyoneDegrees_dataset_header_t {
+} fiftyoneDegreesDataSetHeader;
+
+typedef struct fiftyoneDegrees_workset_pool_t {
+} fiftyoneDegreesWorksetPool;
+
+typedef struct fiftyoneDegrees_property_t {
+} fiftyoneDegreesProperty;
+
+typedef struct fiftyoneDegrees_value_t {
+} fiftyoneDegreesValue;
+
+typedef struct fiftyoneDegrees_resultset_cache_t {
+} fiftyoneDegreesResultsetCache;
+
+typedef struct fiftyoneDegrees_http_header_t {
+ int32_t headerNameOffset;
+ const char *headerName;
+} fiftyoneDegreesHttpHeader;
+
+typedef struct fiftyoneDegrees_http_header_workset_t {
+ fiftyoneDegreesHttpHeader *header;
+ const char *headerValue;
+ int headerValueLength;
+} fiftyoneDegreesHttpHeaderWorkset;
+
+
+typedef struct fiftyoneDegrees_dataset_t {
+ int32_t httpHeadersCount;
+ fiftyoneDegreesHttpHeader *httpHeaders;
+ int32_t requiredPropertyCount;
+ const fiftyoneDegreesProperty **requiredProperties;
+} fiftyoneDegreesDataSet;
+
+typedef struct fiftyoneDegrees_workset_t {
+ fiftyoneDegreesDataSet *dataSet;
+ int32_t importantHeadersCount;
+ fiftyoneDegreesHttpHeaderWorkset *importantHeaders;
+ fiftyoneDegreesMatchMethod method;
+ int32_t difference;
+ const fiftyoneDegreesValue **values;
+} fiftyoneDegreesWorkset;
+
+int32_t fiftyoneDegreesGetSignatureRank(fiftyoneDegreesWorkset *ws);
+
+const char* fiftyoneDegreesGetPropertyName(
+ const fiftyoneDegreesDataSet *dataSet,
+ const fiftyoneDegreesProperty *property);
+
+int32_t fiftyoneDegreesSetValues(
+ fiftyoneDegreesWorkset *ws,
+ int32_t requiredPropertyIndex);
+
+const char* fiftyoneDegreesGetValueName(
+ const fiftyoneDegreesDataSet *dataSet,
+ const fiftyoneDegreesValue *value);
+
+fiftyoneDegreesWorkset *fiftyoneDegreesWorksetPoolGet(
+ fiftyoneDegreesWorksetPool *pool);
+
+void fiftyoneDegreesWorksetPoolRelease(
+ fiftyoneDegreesWorksetPool *pool,
+ fiftyoneDegreesWorkset *ws);
+
+void fiftyoneDegreesMatchForHttpHeaders(fiftyoneDegreesWorkset *ws);
+
+void fiftyoneDegreesMatch(
+ fiftyoneDegreesWorkset *ws,
+ const char* userAgent);
+
+fiftyoneDegreesDataSetInitStatus fiftyoneDegreesInitWithPropertyArray(
+ const char *fileName,
+ fiftyoneDegreesDataSet *dataSet,
+ const char** properties,
+ int32_t count);
+
+fiftyoneDegreesWorksetPool *fiftyoneDegreesWorksetPoolCreate(
+ fiftyoneDegreesDataSet *dataSet,
+ fiftyoneDegreesResultsetCache *cache,
+ int32_t size);
+
+void fiftyoneDegreesWorksetPoolFree(
+ const fiftyoneDegreesWorksetPool *pool);
+
+void fiftyoneDegreesDataSetFree(const fiftyoneDegreesDataSet *dataSet);
+
+const fiftyoneDegreesAsciiString* fiftyoneDegreesGetString(
+ const fiftyoneDegreesDataSet *dataSet,
+ int32_t offset);
+
+#endif \ No newline at end of file
diff --git a/addons/51degrees/dummy/threading.c b/addons/51degrees/dummy/threading.c
new file mode 100644
index 0000000..e65678d
--- /dev/null
+++ b/addons/51degrees/dummy/threading.c
@@ -0,0 +1,4 @@
+typedef struct fiftyoneDegrees_threading_t {
+ // This is an empty structure to ensure a threading.o is generated
+ // by the dummy library, it is never used.
+} dummyFiftyoneDegreesThreading; \ No newline at end of file
diff --git a/addons/51degrees/dummy/trie/51Degrees.c b/addons/51degrees/dummy/trie/51Degrees.c
new file mode 100644
index 0000000..7453061
--- /dev/null
+++ b/addons/51degrees/dummy/trie/51Degrees.c
@@ -0,0 +1,89 @@
+/* *********************************************************************
+ * This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
+ * Copyright 2019 51Degrees Mobile Experts Limited, 5 Charlotte Close,
+ * Caversham, Reading, Berkshire, United Kingdom RG4 7BY
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.
+ *
+ * If a copy of the MPL was not distributed with this file, You can obtain
+ * one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0.
+ * *********************************************************************/
+
+/* *********************************************************************
+ * Dummy library for HAProxy. This does not function, and is designed
+ * solely for HAProxy testing purposes.
+ * *********************************************************************/
+#include "51Degrees.h"
+#include <stdlib.h>
+
+int fiftyoneDegreesGetDeviceOffset(
+ fiftyoneDegreesDataSet *dataSet,
+ const char *userAgent) {
+ return 0;
+}
+
+const char** fiftyoneDegreesGetRequiredPropertiesNames(
+ fiftyoneDegreesDataSet *dataSet) {
+ return NULL;
+}
+
+int fiftyoneDegreesGetRequiredPropertiesCount(
+ fiftyoneDegreesDataSet *dataSet) {
+ return 0;
+}
+
+int fiftyoneDegreesGetValueFromOffsets(
+ fiftyoneDegreesDataSet *dataSet,
+ fiftyoneDegreesDeviceOffsets* deviceOffsets,
+ int requiredPropertyIndex,
+ char* values,
+ int size) {
+ return 0;
+}
+
+static fiftyoneDegreesDeviceOffset dummyOffset = { 0, 0, "dummy-user-agent" };
+
+static fiftyoneDegreesDeviceOffsets dummyOffsets = { 1, &dummyOffset, NULL };
+
+fiftyoneDegreesDeviceOffsets* fiftyoneDegreesCreateDeviceOffsets(
+ fiftyoneDegreesDataSet *dataSet) {
+ return &dummyOffsets;
+}
+
+void fiftyoneDegreesFreeDeviceOffsets(
+ fiftyoneDegreesDeviceOffsets* offsets) {
+ return;
+}
+
+int fiftyoneDegreesGetHttpHeaderCount(
+ fiftyoneDegreesDataSet *dataSet) {
+ return 0;
+}
+
+int fiftyoneDegreesGetHttpHeaderNameOffset(
+ fiftyoneDegreesDataSet *dataSet,
+ int httpHeaderIndex) {
+ return 0;
+}
+
+const char* fiftyoneDegreesGetHttpHeaderNamePointer(
+ fiftyoneDegreesDataSet *dataSet,
+ int httpHeaderIndex) {
+ return "dummy-header-name";
+}
+
+fiftyoneDegreesDataSetInitStatus fiftyoneDegreesInitWithPropertyArray(
+ const char* fileName,
+ fiftyoneDegreesDataSet *dataSet,
+ const char** properties,
+ int propertyCount) {
+ return DATA_SET_INIT_STATUS_SUCCESS;
+}
+
+void fiftyoneDegreesDataSetFree(fiftyoneDegreesDataSet *dataSet) {
+ return;
+} \ No newline at end of file
diff --git a/addons/51degrees/dummy/trie/51Degrees.h b/addons/51degrees/dummy/trie/51Degrees.h
new file mode 100644
index 0000000..bedcfd7
--- /dev/null
+++ b/addons/51degrees/dummy/trie/51Degrees.h
@@ -0,0 +1,112 @@
+/* *********************************************************************
+ * This Source Code Form is copyright of 51Degrees Mobile Experts Limited.
+ * Copyright 2019 51Degrees Mobile Experts Limited, 5 Charlotte Close,
+ * Caversham, Reading, Berkshire, United Kingdom RG4 7BY
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0.
+ *
+ * If a copy of the MPL was not distributed with this file, You can obtain
+ * one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0.
+ * *********************************************************************/
+
+/* *********************************************************************
+ * Dummy library for HAProxy. This does not function, and is designed
+ * solely for HAProxy testing purposes.
+ * *********************************************************************/
+#ifndef FIFTYONEDEGREES_H_INCLUDED
+#define FIFTYONEDEGREES_H_INCLUDED
+
+#ifndef FIFTYONEDEGREES_H_TRIE_INCLUDED
+#define FIFTYONEDEGREES_H_TRIE_INCLUDED
+#endif
+
+#ifndef FIFTYONEDEGREES_DUMMY_LIB
+#define FIFTYONEDEGREES_DUMMY_LIB
+#endif
+
+#include <stdint.h>
+
+typedef enum e_fiftyoneDegrees_DataSetInitStatus {
+ DATA_SET_INIT_STATUS_SUCCESS,
+ DATA_SET_INIT_STATUS_INSUFFICIENT_MEMORY,
+ DATA_SET_INIT_STATUS_CORRUPT_DATA,
+ DATA_SET_INIT_STATUS_INCORRECT_VERSION,
+ DATA_SET_INIT_STATUS_FILE_NOT_FOUND,
+ DATA_SET_INIT_STATUS_NOT_SET,
+ DATA_SET_INIT_STATUS_POINTER_OUT_OF_BOUNDS,
+ DATA_SET_INIT_STATUS_NULL_POINTER
+} fiftyoneDegreesDataSetInitStatus;
+
+typedef struct fiftyoneDegrees_integers_t {
+ int32_t *firstElement;
+ unsigned int count;
+ int freeMemory;
+} fiftyoneDegreesIntegers;
+
+typedef struct fiftyoneDegrees_dataset_t {
+ fiftyoneDegreesIntegers uniqueHttpHeaders;
+} fiftyoneDegreesDataSet;
+
+typedef struct fiftyoneDegrees_active_dataset_t {
+
+} fiftyoneDegreesActiveDataSet;
+
+typedef struct fiftyoneDegrees_device_offset_t {
+ int httpHeaderOffset;
+ int deviceOffset;
+ char *userAgent;
+} fiftyoneDegreesDeviceOffset;
+
+typedef struct fiftyoneDegrees_device_offsets_t {
+ int size;
+ fiftyoneDegreesDeviceOffset *firstOffset;
+ fiftyoneDegreesActiveDataSet *active;
+} fiftyoneDegreesDeviceOffsets;
+
+int fiftyoneDegreesGetDeviceOffset(
+ fiftyoneDegreesDataSet *dataSet,
+ const char *userAgent);
+
+const char** fiftyoneDegreesGetRequiredPropertiesNames(
+ fiftyoneDegreesDataSet *dataSet);
+
+int fiftyoneDegreesGetRequiredPropertiesCount(
+ fiftyoneDegreesDataSet *dataSet);
+
+int fiftyoneDegreesGetValueFromOffsets(
+ fiftyoneDegreesDataSet *dataSet,
+ fiftyoneDegreesDeviceOffsets* deviceOffsets,
+ int requiredPropertyIndex,
+ char* values,
+ int size);
+
+fiftyoneDegreesDeviceOffsets* fiftyoneDegreesCreateDeviceOffsets(
+ fiftyoneDegreesDataSet *dataSet);
+
+void fiftyoneDegreesFreeDeviceOffsets(
+ fiftyoneDegreesDeviceOffsets* offsets);
+
+int fiftyoneDegreesGetHttpHeaderCount(
+ fiftyoneDegreesDataSet *dataSet);
+
+int fiftyoneDegreesGetHttpHeaderNameOffset(
+ fiftyoneDegreesDataSet *dataSet,
+ int httpHeaderIndex);
+
+const char* fiftyoneDegreesGetHttpHeaderNamePointer(
+ fiftyoneDegreesDataSet *dataSet,
+ int httpHeaderIndex);
+
+fiftyoneDegreesDataSetInitStatus fiftyoneDegreesInitWithPropertyArray(
+ const char* fileName,
+ fiftyoneDegreesDataSet *dataSet,
+ const char** properties,
+ int propertyCount);
+
+void fiftyoneDegreesDataSetFree(fiftyoneDegreesDataSet *dataSet);
+
+#endif \ No newline at end of file
diff --git a/addons/deviceatlas/Makefile b/addons/deviceatlas/Makefile
new file mode 100644
index 0000000..fbcffca
--- /dev/null
+++ b/addons/deviceatlas/Makefile
@@ -0,0 +1,48 @@
+# DEVICEATLAS_SRC : DeviceAtlas API source root path
+
+
+OS := $(shell uname -s)
+OBJS := dadwsch.o
+CFLAGS := -g -O2
+LDFLAGS :=
+
+CURL_CONFIG := curl-config
+CURLDIR := $(shell $(CURL_CONFIG) --prefix 2>/dev/null || echo /usr/local)
+CURL_INC := $(CURLDIR)/include
+CURL_LIB := $(CURLDIR)/lib
+CURL_LDFLAGS := $(shell $(CURL_CONFIG) --libs 2>/dev/null || echo -L /usr/local/lib -lcurl)
+
+PCRE2_CONFIG := pcre2-config
+PCRE2DIR := $(shell $(PCRE2_CONFIG) --prefix 2>/dev/null || echo /usr/local)
+PCRE2_INC := $(PCRE2DIR)/include
+PCRE2_LIB := $(PCRE2DIR)/lib
+PCRE2_LDFLAGS := $(shell $(PCRE2_CONFIG) --libs8 2>/dev/null || echo /usr/local)
+
+ifeq ($(DEVICEATLAS_SRC),)
+dadwsch: dadwsch.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+LDFLAGS += -lda
+else
+DEVICEATLAS_INC = $(DEVICEATLAS_SRC)
+DEVICEATLAS_LIB = $(DEVICEATLAS_SRC)
+CFLAGS += -DDA_REGEX_HDR=\"dac_pcre2.c\" -DDA_REGEX_TAG=2
+CFLAGS += -DMOBI_CURL -DMOBI_CURLSSET -DMOBI_GZ -DMOBI_ZIP
+CFLAGS += -I$(DEVICEATLAS_INC) -I$(CURL_INC) -I$(PCRE2DIR)
+LDFLAGS += $(CURL_LDFLAGS) $(PCRE2_LDFLAGS) -lz -lzip -lpthread
+
+dadwsch: dadwsch.c $(DEVICEATLAS_SRC)/dac.c $(DEVICEATLAS_SRC)/dasch.c $(DEVICEATLAS_SRC)/dadwarc.c $(DEVICEATLAS_SRC)/dadwcom.c $(DEVICEATLAS_SRC)/dadwcurl.c $(DEVICEATLAS_SRC)/json.c $(DEVICEATLAS_SRC)/Os/daunix.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+endif
+
+ifeq ($(OS), Linux)
+LDFLAGS += -lrt
+endif
+ifeq ($(OS), SunOS)
+LDFLAGS += -lrt
+endif
+
+clean:
+ rm -f *.o
+ rm -f $(DEVICEATLAS_LIB)*.o
+ rm -f dadwsch
diff --git a/addons/deviceatlas/da.c b/addons/deviceatlas/da.c
new file mode 100644
index 0000000..969dfaa
--- /dev/null
+++ b/addons/deviceatlas/da.c
@@ -0,0 +1,501 @@
+#include <stdio.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <errno.h>
+
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/http.h>
+#include <haproxy/http_ana.h>
+#include <haproxy/http_fetch.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/htx.h>
+#include <haproxy/sample.h>
+#include <haproxy/tools.h>
+#include <dac.h>
+
+#define ATLASTOKSZ PATH_MAX
+#define ATLASMAPNM "/hapdeviceatlas"
+
+static struct {
+ void *atlasimgptr;
+ void *atlasmap;
+ char *jsonpath;
+ char *cookiename;
+ size_t cookienamelen;
+ int atlasfd;
+ da_atlas_t atlas;
+ da_evidence_id_t useragentid;
+ da_severity_t loglevel;
+ char separator;
+ unsigned char daset:1;
+} global_deviceatlas = {
+ .loglevel = 0,
+ .jsonpath = 0,
+ .cookiename = 0,
+ .cookienamelen = 0,
+ .atlasmap = NULL,
+ .atlasfd = -1,
+ .useragentid = 0,
+ .daset = 0,
+ .separator = '|',
+};
+
+__decl_thread(HA_SPINLOCK_T dadwsch_lock);
+
+static int da_json_file(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas json file : expects a json path.\n");
+ return -1;
+ }
+ global_deviceatlas.jsonpath = strdup(args[1]);
+ return 0;
+}
+
+static int da_log_level(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int loglevel;
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas log level : expects an integer argument.\n");
+ return -1;
+ }
+
+ loglevel = atol(args[1]);
+ if (loglevel < 0 || loglevel > 3) {
+ memprintf(err, "deviceatlas log level : expects a log level between 0 and 3, %s given.\n", args[1]);
+ } else {
+ global_deviceatlas.loglevel = (da_severity_t)loglevel;
+ }
+
+ return 0;
+}
+
+static int da_property_separator(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas property separator : expects a character argument.\n");
+ return -1;
+ }
+ global_deviceatlas.separator = *args[1];
+ return 0;
+}
+
+static int da_properties_cookie(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "deviceatlas cookie name : expects a string argument.\n");
+ return -1;
+ } else {
+ global_deviceatlas.cookiename = strdup(args[1]);
+ }
+ global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
+ return 0;
+}
+
+static size_t da_haproxy_read(void *ctx, size_t len, char *buf)
+{
+ return fread(buf, 1, len, ctx);
+}
+
+static da_status_t da_haproxy_seek(void *ctx, off_t off)
+{
+ return fseek(ctx, off, SEEK_SET) != -1 ? DA_OK : DA_SYS;
+}
+
+static void da_haproxy_log(da_severity_t severity, da_status_t status,
+ const char *fmt, va_list args)
+{
+ if (global_deviceatlas.loglevel && severity <= global_deviceatlas.loglevel) {
+ char logbuf[256];
+ vsnprintf(logbuf, sizeof(logbuf), fmt, args);
+ ha_warning("deviceatlas : %s.\n", logbuf);
+ }
+}
+
+#define DA_COOKIENAME_DEFAULT "DAPROPS"
+
+/*
+ * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
+ */
+static int init_deviceatlas(void)
+{
+ int err_code = ERR_NONE;
+
+ if (global_deviceatlas.jsonpath != 0) {
+ FILE *jsonp;
+ da_property_decl_t extraprops[] = {{0, 0}};
+ size_t atlasimglen;
+ da_status_t status;
+
+ jsonp = fopen(global_deviceatlas.jsonpath, "r");
+ if (jsonp == 0) {
+ ha_alert("deviceatlas : '%s' json file has invalid path or is not readable.\n",
+ global_deviceatlas.jsonpath);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ da_init();
+ da_seterrorfunc(da_haproxy_log);
+ status = da_atlas_compile(jsonp, da_haproxy_read, da_haproxy_seek,
+ &global_deviceatlas.atlasimgptr, &atlasimglen);
+ fclose(jsonp);
+ if (status != DA_OK) {
+ ha_alert("deviceatlas : '%s' json file is invalid.\n",
+ global_deviceatlas.jsonpath);
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ status = da_atlas_open(&global_deviceatlas.atlas, extraprops,
+ global_deviceatlas.atlasimgptr, atlasimglen);
+
+ if (status != DA_OK) {
+ ha_alert("deviceatlas : data could not be compiled.\n");
+ err_code |= ERR_ALERT | ERR_FATAL;
+ goto out;
+ }
+
+ if (global_deviceatlas.cookiename == 0) {
+ global_deviceatlas.cookiename = strdup(DA_COOKIENAME_DEFAULT);
+ global_deviceatlas.cookienamelen = strlen(global_deviceatlas.cookiename);
+ }
+
+ global_deviceatlas.useragentid = da_atlas_header_evidence_id(&global_deviceatlas.atlas,
+ "user-agent");
+ if ((global_deviceatlas.atlasfd = shm_open(ATLASMAPNM, O_RDWR, 0660)) != -1) {
+ global_deviceatlas.atlasmap = mmap(NULL, ATLASTOKSZ, PROT_READ | PROT_WRITE, MAP_SHARED, global_deviceatlas.atlasfd, 0);
+ if (global_deviceatlas.atlasmap == MAP_FAILED) {
+ close(global_deviceatlas.atlasfd);
+ global_deviceatlas.atlasfd = -1;
+ global_deviceatlas.atlasmap = NULL;
+ } else {
+ fprintf(stdout, "Deviceatlas : scheduling support enabled.\n");
+ }
+ }
+ global_deviceatlas.daset = 1;
+
+ fprintf(stdout, "Deviceatlas module loaded.\n");
+ }
+
+out:
+ return err_code;
+}
+
+static void deinit_deviceatlas(void)
+{
+ if (global_deviceatlas.jsonpath != 0) {
+ free(global_deviceatlas.jsonpath);
+ }
+
+ if (global_deviceatlas.daset == 1) {
+ free(global_deviceatlas.cookiename);
+ da_atlas_close(&global_deviceatlas.atlas);
+ free(global_deviceatlas.atlasimgptr);
+ }
+
+ if (global_deviceatlas.atlasfd != -1) {
+ munmap(global_deviceatlas.atlasmap, ATLASTOKSZ);
+ close(global_deviceatlas.atlasfd);
+ shm_unlink(ATLASMAPNM);
+ }
+
+ da_fini();
+}
+
+static void da_haproxy_checkinst(void)
+{
+ if (global_deviceatlas.atlasmap != 0) {
+ char *base;
+ base = (char *)global_deviceatlas.atlasmap;
+
+ if (base[0] != 0) {
+ void *cnew;
+ size_t atlassz;
+ char atlasp[ATLASTOKSZ] = {0};
+ da_atlas_t inst;
+ da_property_decl_t extraprops[1] = {{NULL, 0}};
+#ifdef USE_THREAD
+ HA_SPIN_LOCK(OTHER_LOCK, &dadwsch_lock);
+#endif
+ strlcpy2(atlasp, base, sizeof(atlasp));
+ if (da_atlas_read_mapped(atlasp, NULL, &cnew, &atlassz) == DA_OK) {
+ if (da_atlas_open(&inst, extraprops, cnew, atlassz) == DA_OK) {
+ char jsonbuf[26];
+ time_t jsond;
+
+ da_atlas_close(&global_deviceatlas.atlas);
+ free(global_deviceatlas.atlasimgptr);
+ global_deviceatlas.atlasimgptr = cnew;
+ global_deviceatlas.atlas = inst;
+ memset(base, 0, ATLASTOKSZ);
+ jsond = da_getdatacreation(&global_deviceatlas.atlas);
+ ctime_r(&jsond, jsonbuf);
+ jsonbuf[24] = 0;
+ printf("deviceatlas: new instance, data file date `%s`.\n", jsonbuf);
+ } else {
+ ha_warning("deviceatlas: instance update failed.\n");
+ memset(base, 0, ATLASTOKSZ);
+ free(cnew);
+ }
+ }
+#ifdef USE_THREAD
+ HA_SPIN_UNLOCK(OTHER_LOCK, &dadwsch_lock);
+#endif
+ }
+ }
+}
+
+static int da_haproxy(const struct arg *args, struct sample *smp, da_deviceinfo_t *devinfo)
+{
+ struct buffer *tmp;
+ da_propid_t prop, *pprop;
+ da_status_t status;
+ da_type_t proptype;
+ const char *propname;
+ int i;
+
+ tmp = get_trash_chunk();
+ chunk_reset(tmp);
+
+ propname = (const char *) args[0].data.str.area;
+ i = 0;
+
+ for (; propname != 0; i ++,
+ propname = (const char *) args[i].data.str.area) {
+ status = da_atlas_getpropid(&global_deviceatlas.atlas,
+ propname, &prop);
+ if (status != DA_OK) {
+ chunk_appendf(tmp, "%c", global_deviceatlas.separator);
+ continue;
+ }
+ pprop = &prop;
+ da_atlas_getproptype(&global_deviceatlas.atlas, *pprop, &proptype);
+
+ switch (proptype) {
+ case DA_TYPE_BOOLEAN: {
+ bool val;
+ status = da_getpropboolean(devinfo, *pprop, &val);
+ if (status == DA_OK) {
+ chunk_appendf(tmp, "%d", val);
+ }
+ break;
+ }
+ case DA_TYPE_INTEGER:
+ case DA_TYPE_NUMBER: {
+ long val;
+ status = da_getpropinteger(devinfo, *pprop, &val);
+ if (status == DA_OK) {
+ chunk_appendf(tmp, "%ld", val);
+ }
+ break;
+ }
+ case DA_TYPE_STRING: {
+ const char *val;
+ status = da_getpropstring(devinfo, *pprop, &val);
+ if (status == DA_OK) {
+ chunk_appendf(tmp, "%s", val);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ chunk_appendf(tmp, "%c", global_deviceatlas.separator);
+ }
+
+ da_close(devinfo);
+
+ if (tmp->data) {
+ --tmp->data;
+ tmp->area[tmp->data] = 0;
+ }
+
+ smp->data.u.str.area = tmp->area;
+ smp->data.u.str.data = tmp->data;
+ smp->data.type = SMP_T_STR;
+
+ return 1;
+}
+
+static int da_haproxy_conv(const struct arg *args, struct sample *smp, void *private)
+{
+ da_deviceinfo_t devinfo;
+ da_status_t status;
+ const char *useragent;
+ char useragentbuf[1024] = { 0 };
+ int i;
+
+ if (global_deviceatlas.daset == 0 || smp->data.u.str.data == 0) {
+ return 1;
+ }
+
+ da_haproxy_checkinst();
+
+ i = smp->data.u.str.data > sizeof(useragentbuf) ? sizeof(useragentbuf) : smp->data.u.str.data;
+ memcpy(useragentbuf, smp->data.u.str.area, i - 1);
+ useragentbuf[i - 1] = 0;
+
+ useragent = (const char *)useragentbuf;
+
+ status = da_search(&global_deviceatlas.atlas, &devinfo,
+ global_deviceatlas.useragentid, useragent, 0);
+
+ return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
+}
+
+#define DA_MAX_HEADERS 24
+
+static int da_haproxy_fetch(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+ da_evidence_t ev[DA_MAX_HEADERS];
+ da_deviceinfo_t devinfo;
+ da_status_t status;
+ struct channel *chn;
+ struct htx *htx;
+ struct htx_blk *blk;
+ char vbuf[DA_MAX_HEADERS][1024] = {{ 0 }};
+ int i, nbh = 0;
+
+ if (global_deviceatlas.daset == 0) {
+ return 0;
+ }
+
+ da_haproxy_checkinst();
+
+ chn = (smp->strm ? &smp->strm->req : NULL);
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ if (!htx)
+ return 0;
+
+ i = 0;
+ for (blk = htx_get_first_blk(htx); nbh < DA_MAX_HEADERS && blk; blk = htx_get_next_blk(htx, blk)) {
+ size_t vlen;
+ char *pval;
+ da_evidence_id_t evid;
+ enum htx_blk_type type;
+ struct ist n, v;
+ char hbuf[24] = { 0 };
+ char tval[1024] = { 0 };
+
+ type = htx_get_blk_type(blk);
+
+ if (type == HTX_BLK_HDR) {
+ n = htx_get_blk_name(htx, blk);
+ v = htx_get_blk_value(htx, blk);
+ } else if (type == HTX_BLK_EOH) {
+ break;
+ } else {
+ continue;
+ }
+
+ /* The HTTP headers used by the DeviceAtlas API are not longer */
+ if (n.len >= sizeof(hbuf)) {
+ continue;
+ }
+
+ memcpy(hbuf, n.ptr, n.len);
+ hbuf[n.len] = 0;
+ pval = v.ptr;
+ vlen = v.len;
+ evid = -1;
+ i = v.len > sizeof(tval) - 1 ? sizeof(tval) - 1 : v.len;
+ memcpy(tval, v.ptr, i);
+ tval[i] = 0;
+ pval = tval;
+
+ if (strcasecmp(hbuf, "Accept-Language") == 0) {
+ evid = da_atlas_accept_language_evidence_id(&global_deviceatlas.atlas);
+ } else if (strcasecmp(hbuf, "Cookie") == 0) {
+ char *p, *eval;
+ size_t pl;
+
+ eval = pval + vlen;
+ /**
+ * The cookie value, if it exists, is located between the current header's
+ * value position and the next one
+ */
+ if (http_extract_cookie_value(pval, eval, global_deviceatlas.cookiename,
+ global_deviceatlas.cookienamelen, 1, &p, &pl) == NULL) {
+ continue;
+ }
+
+ vlen -= global_deviceatlas.cookienamelen - 1;
+ pval = p;
+ evid = da_atlas_clientprop_evidence_id(&global_deviceatlas.atlas);
+ } else {
+ evid = da_atlas_header_evidence_id(&global_deviceatlas.atlas, hbuf);
+ }
+
+ if (evid == -1) {
+ continue;
+ }
+
+ i = vlen > sizeof(vbuf[nbh]) - 1 ? sizeof(vbuf[nbh]) - 1 : vlen;
+ memcpy(vbuf[nbh], pval, i);
+ vbuf[nbh][i] = 0;
+ ev[nbh].key = evid;
+ ev[nbh].value = vbuf[nbh];
+ ++ nbh;
+ }
+
+ status = da_searchv(&global_deviceatlas.atlas, &devinfo,
+ ev, nbh);
+
+ return status != DA_OK ? 0 : da_haproxy(args, smp, &devinfo);
+}
+
+static struct cfg_kw_list dacfg_kws = {{ }, {
+ { CFG_GLOBAL, "deviceatlas-json-file", da_json_file },
+ { CFG_GLOBAL, "deviceatlas-log-level", da_log_level },
+ { CFG_GLOBAL, "deviceatlas-property-separator", da_property_separator },
+ { CFG_GLOBAL, "deviceatlas-properties-cookie", da_properties_cookie },
+ { 0, NULL, NULL },
+}};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &dacfg_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list fetch_kws = {ILH, {
+ { "da-csv-fetch", da_haproxy_fetch, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
+ { NULL, NULL, 0, 0, 0 },
+}};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &fetch_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_conv_kw_list conv_kws = {ILH, {
+ { "da-csv-conv", da_haproxy_conv, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_T_STR },
+ { NULL, NULL, 0, 0, 0 },
+}};
+
+static void da_haproxy_register_build_options()
+{
+ char *ptr = NULL;
+
+#ifdef MOBI_DA_DUMMY_LIBRARY
+ memprintf(&ptr, "Built with DeviceAtlas support (dummy library only).");
+#else
+ memprintf(&ptr, "Built with DeviceAtlas support (library version %u.%u).", MOBI_DA_MAJOR, MOBI_DA_MINOR);
+#endif
+ hap_register_build_opts(ptr, 1);
+}
+
+INITCALL1(STG_REGISTER, sample_register_convs, &conv_kws);
+
+REGISTER_POST_CHECK(init_deviceatlas);
+REGISTER_POST_DEINIT(deinit_deviceatlas);
+INITCALL0(STG_REGISTER, da_haproxy_register_build_options);
diff --git a/addons/deviceatlas/dadwsch.c b/addons/deviceatlas/dadwsch.c
new file mode 100644
index 0000000..e35566a
--- /dev/null
+++ b/addons/deviceatlas/dadwsch.c
@@ -0,0 +1,195 @@
+#define _GNU_SOURCE
+#include <dac.h>
+#include <dadwcurl.h>
+#include <dadwarc.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#define ATLASTOKSZ PATH_MAX
+#define ATLASMAPNM "/hapdeviceatlas"
+
+const char *__pgname;
+
+static struct {
+ da_dwatlas_t o;
+ int ofd;
+ void* atlasmap;
+} global_deviceatlassch = {
+ .ofd = -1,
+ .atlasmap = NULL
+};
+
+
+void usage(void)
+{
+ fprintf(stderr, "%s -u download URL [-d hour (in H:M:S format) current hour by default] [-p path for the downloaded file, /tmp by default]\n", __pgname);
+ exit(EXIT_FAILURE);
+}
+
+static size_t jsonread(void *ctx, size_t count, char *buf)
+{
+ return fread(buf, 1, count, ctx);
+}
+
+static da_status_t jsonseek(void *ctx, off_t pos)
+{
+ return fseek(ctx, pos, SEEK_SET) != -1 ? DA_OK : DA_SYS;
+}
+
+static void dadwlog(dw_config_t cfg, const char* msg)
+{
+ time_t now = time(NULL);
+ char buf[26] = {0};
+ ctime_r(&now, buf);
+ buf[24] = 0;
+ fprintf(stderr, "%s: %s\n", buf, msg);
+}
+
+static dw_status_t dadwnot(void *a, dw_config_t *cfg)
+{
+ da_dwatlas_t *o = (da_dwatlas_t *)a;
+ if (!o)
+ return DW_ERR;
+ char *e;
+ char jsondbuf[26] = {0}, buf[26] = {0}, atlasp[ATLASTOKSZ] = {0};
+ time_t now = time(NULL);
+ time_t jsond;
+ int fd = -1;
+ (void)a;
+ jsond = da_getdatacreation(&o->atlas);
+ dwgetfinalp(o->dcfg.info, atlasp, sizeof(atlasp));
+ ctime_r(&jsond, jsondbuf);
+ ctime_r(&now, buf);
+ jsondbuf[24] = 0;
+ buf[24] = 0;
+
+ printf("%s: data file generated on `%s`\n", buf, jsondbuf);
+ int val = 1;
+ unsigned char *ptr = (unsigned char *)global_deviceatlassch.atlasmap;
+ memset(ptr, 0, sizeof(atlasp));
+ strcpy(ptr, atlasp);
+ return DW_OK;
+}
+
+static da_status_t dadwinit(void)
+{
+ if ((global_deviceatlassch.ofd = shm_open(ATLASMAPNM, O_RDWR | O_CREAT, 0660)) == -1) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return DA_SYS;
+ }
+
+ if (ftruncate(global_deviceatlassch.ofd, ATLASTOKSZ) == -1) {
+ close(global_deviceatlassch.ofd);
+ return DA_SYS;
+ }
+ lseek(global_deviceatlassch.ofd, 0, SEEK_SET);
+ global_deviceatlassch.atlasmap = mmap(0, ATLASTOKSZ, PROT_READ | PROT_WRITE, MAP_SHARED, global_deviceatlassch.ofd, 0);
+ if (global_deviceatlassch.atlasmap == MAP_FAILED) {
+ fprintf(stderr, "%s\n", strerror(errno));
+ return DA_SYS;
+ } else {
+ memset(global_deviceatlassch.atlasmap, 0, ATLASTOKSZ);
+ return DA_OK;
+ }
+}
+
+static void dadwexit(int sig __attribute__((unused)), siginfo_t *s __attribute__((unused)), void *ctx __attribute__((unused)))
+{
+ ssize_t w;
+
+ fprintf(stderr, "%s: exit\n", __pgname);
+ dw_daatlas_close(&global_deviceatlassch.o);
+ da_fini();
+ munmap(global_deviceatlassch.atlasmap, ATLASTOKSZ);
+ close(global_deviceatlassch.ofd);
+ shm_unlink(ATLASMAPNM);
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ const char *opts = "u:p:d:h";
+ bool dset = false;
+ size_t i;
+ int ch;
+
+ da_property_decl_t extraprops[1] = {
+ { 0, 0 }
+ };
+
+ __pgname = argv[0];
+
+ dw_df_dainit_fn = curldwinit;
+ dw_df_dacleanup_fn = curldwcleanup;
+
+ da_init();
+ memset(&global_deviceatlassch.o.dcfg, 0, sizeof(global_deviceatlassch.o.dcfg));
+ while ((ch = getopt(argc, argv, opts)) != -1) {
+ switch (ch) {
+ case 'u':
+ global_deviceatlassch.o.dcfg.info.url = strdup(optarg);
+ break;
+ case 'p':
+ global_deviceatlassch.o.dcfg.info.path = strdup(optarg);
+ break;
+ case 'd':
+ if (strptime(optarg, "%H:%M:%S", &global_deviceatlassch.o.dcfg.info.rtm) != NULL)
+ dset = true;
+ else
+ usage();
+ break;
+ case 'h':
+ default:
+ usage();
+ }
+ }
+
+ if (!dset) {
+ time_t now = time(NULL);
+ struct tm *cnow = gmtime(&now);
+ memcpy(&global_deviceatlassch.o.dcfg.info.rtm, cnow, offsetof(struct tm, tm_mday));
+ }
+
+ if (!global_deviceatlassch.o.dcfg.info.url)
+ usage();
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_flags = SA_SIGINFO | SA_RESTART;
+ sa.sa_sigaction = dadwexit;
+
+ global_deviceatlassch.o.dcfg.info.datatm = 1;
+ global_deviceatlassch.o.dcfg.info.chksum = 1;
+ global_deviceatlassch.o.dcfg.info.reload = 1;
+ global_deviceatlassch.o.dcfg.info.tobin = 1;
+ global_deviceatlassch.o.dcfg.ep = extraprops;
+ global_deviceatlassch.o.dcfg.dwproc = curldwproc;
+ global_deviceatlassch.o.dcfg.dwextract = dadwextract;
+ global_deviceatlassch.o.dcfg.lptr = (void *)stderr;
+ global_deviceatlassch.o.dcfg.dwlog = &dadwlog;
+ global_deviceatlassch.o.dcfg.dwnotify_n = &dadwnot;
+ global_deviceatlassch.o.rfn = jsonread;
+ global_deviceatlassch.o.posfn = jsonseek;
+
+ if (dadwinit() != DA_OK) {
+ fprintf(stderr, "%s init failed\n", __pgname);
+ exit(EXIT_FAILURE);
+ }
+
+ if (da_atlas_open_schedule(&global_deviceatlassch.o) != DA_OK) {
+ fprintf(stderr, "%s scheduling failed\n", __pgname);
+ exit(EXIT_FAILURE);
+ }
+
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaction(SIGTERM, &sa, NULL);
+
+ while (true) sleep(1);
+
+ return 0;
+}
diff --git a/addons/deviceatlas/dummy/Makefile b/addons/deviceatlas/dummy/Makefile
new file mode 100644
index 0000000..8bba840
--- /dev/null
+++ b/addons/deviceatlas/dummy/Makefile
@@ -0,0 +1,12 @@
+# makefile for dummy DeviceAtlas library
+#
+# To enable the DeviceAtlas module support, the following are needed
+# make TARGET=<target> DEVICEATLAS_SRC=addons/deviceatlas/dummy USE_PCRE=1 USE_DEVICEATLAS=1
+
+build: libda.a
+
+libda.a: dac.o
+ ar rv $@ $<
+
+clean:
+ rm -rf *.a *.o
diff --git a/addons/deviceatlas/dummy/Os/daunix.c b/addons/deviceatlas/dummy/Os/daunix.c
new file mode 100644
index 0000000..ca696f9
--- /dev/null
+++ b/addons/deviceatlas/dummy/Os/daunix.c
@@ -0,0 +1,9 @@
+#include "dac.h"
+
+static char const __attribute__((unused)) rcsid[] = "$Id: dac.c, v dummy 1970/01/01 00:00:01 dcarlier Exp $";
+
+da_status_t
+da_atlas_read_mapped(const char *path, void *m, void **p, size_t *l)
+{
+ return DA_SYS;
+}
diff --git a/addons/deviceatlas/dummy/dac.c b/addons/deviceatlas/dummy/dac.c
new file mode 100644
index 0000000..720dc6a
--- /dev/null
+++ b/addons/deviceatlas/dummy/dac.c
@@ -0,0 +1,222 @@
+#include "dac.h"
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+static char const __attribute__((unused)) rcsid[] = "$Id: dac.c, v dummy 1970/01/01 00:00:01 dcarlier Exp $";
+
+struct da_bitset {
+ unsigned long bits[8];
+ size_t bit_count;
+};
+
+/*
+ * Constructor/Destructor for possible globals.
+ */
+
+void
+da_init()
+{
+}
+
+void
+da_fini()
+{
+}
+
+
+void
+da_seterrorfunc(da_errorfunc_t callback)
+{
+}
+
+const char *
+da_typename(da_type_t fieldtype)
+{
+ return "none";
+}
+
+char *
+da_getdataversion(da_atlas_t *atlas)
+{
+ return "dummy library version 1.0";
+}
+
+time_t
+da_getdatacreation(da_atlas_t *atlas)
+{
+ return time(NULL);
+}
+
+int
+da_getdatarevision(da_atlas_t *atlas)
+{
+ return 1;
+}
+
+da_status_t
+da_atlas_compile(void *ctx, da_read_fn readfn, da_setpos_fn rewind, void **ptr, size_t *size)
+{
+ return DA_OK;
+}
+
+da_status_t
+da_atlas_open(da_atlas_t *atlas, da_property_decl_t *extraprops, const void *ptr, size_t len)
+{
+ void *ptr2 = malloc(len);
+ free(ptr2);
+ return ptr2 ? DA_OK : DA_NOMEM;
+}
+
+void
+da_atlas_close(da_atlas_t *atlas)
+{
+}
+
+da_evidence_id_t
+da_atlas_clientprop_evidence_id(const da_atlas_t *atlas)
+{
+ return (da_evidence_id_t)2;
+}
+
+da_evidence_id_t
+da_atlas_accept_language_evidence_id(const da_atlas_t *atlas)
+{
+ return (da_evidence_id_t)3;
+}
+
+da_evidence_id_t
+da_atlas_header_evidence_id(const da_atlas_t *atlas, const char *evidence_name)
+{
+ return (da_evidence_id_t)1;
+}
+
+da_status_t
+da_atlas_getproptype(const da_atlas_t *atlas, da_propid_t propid, da_type_t *type)
+{
+ *type = DA_TYPE_BOOLEAN;
+ return DA_OK;
+}
+
+da_status_t
+da_atlas_getpropname(const da_atlas_t *atlas, da_propid_t propid, const char **name)
+{
+ *name = "isRobot";
+ return DA_OK;
+}
+
+da_status_t
+da_atlas_getpropid(const da_atlas_t *atlas, const char *propname, da_propid_t *property)
+{
+ *property = (da_propid_t)1;
+ return DA_OK;
+}
+
+size_t
+da_atlas_getpropcount(const da_atlas_t *atlas)
+{
+ return 1;
+}
+
+void
+da_atlas_setconfig(da_atlas_t *atlas, da_config_t *config)
+{
+}
+
+da_status_t
+da_searchv(const da_atlas_t *atlas, da_deviceinfo_t *result, da_evidence_t *evidence, size_t count)
+{
+ memset(result, 0, sizeof(*result));
+ result->propcount = count;
+ return DA_OK;
+}
+
+da_status_t
+da_search(const da_atlas_t *atlas, da_deviceinfo_t *result, ...)
+{
+ da_evidence_t vec[4]; /* XXX: this will have to grow if more evidence is supported. */
+ size_t i;
+ va_list args;
+ va_start(args, result);
+ for (i = 0; i < sizeof vec / sizeof vec[0];) {
+ vec[i].key = va_arg(args, da_evidence_id_t);
+ if (vec[i].key == 0)
+ break;
+ vec[i++].value = va_arg(args, char *);
+ }
+ va_end(args);
+ return da_searchv(atlas, result, vec, i);
+}
+
+/*
+ * Search-result centric functions.
+ */
+size_t
+da_getpropcount(const da_deviceinfo_t *info)
+{
+ return info->propcount;
+}
+
+da_status_t
+da_getfirstprop(const da_deviceinfo_t *info, da_propid_t **propid)
+{
+ if (info->propcount == 0)
+ return DA_NOMORE;
+ *propid = &info->proplist[0];
+ return DA_OK;
+}
+
+da_status_t
+da_getnextprop(const da_deviceinfo_t *info, da_propid_t **propid)
+{
+ if (*propid - info->proplist >= info->propcount - 1)
+ return DA_NOMORE;
+ ++*propid;
+ return DA_OK;
+}
+
+void
+da_close(da_deviceinfo_t *sr)
+{
+}
+
+da_status_t
+da_getpropname(const da_deviceinfo_t *info, da_propid_t propid, const char **name)
+{
+ *name = "isRobot";
+ return DA_OK;
+}
+
+da_status_t
+da_getproptype(const da_deviceinfo_t *info, da_propid_t propid, da_type_t *type)
+{
+ *type = DA_TYPE_BOOLEAN;
+ return DA_OK;
+}
+
+da_status_t
+da_getpropinteger(const da_deviceinfo_t *info, da_propid_t property, long *vp)
+{
+ *vp = -1;
+ return DA_OK;
+}
+
+da_status_t
+da_getpropstring(const da_deviceinfo_t *info, da_propid_t property, const char **vp)
+{
+ *vp = NULL;
+ return DA_OK;
+}
+
+da_status_t
+da_getpropboolean(const da_deviceinfo_t *info, da_propid_t property, bool *vp)
+{
+ *vp = true;
+ return DA_OK;
+}
+
+const char *
+da_get_property_name(const da_atlas_t *atlas, da_propid_t property)
+{
+ return "isRobot";
+}
diff --git a/addons/deviceatlas/dummy/dac.h b/addons/deviceatlas/dummy/dac.h
new file mode 100644
index 0000000..bf166ae
--- /dev/null
+++ b/addons/deviceatlas/dummy/dac.h
@@ -0,0 +1,600 @@
+#ifndef MOBI_DA_DAC_H
+#define MOBI_DA_DAC_H
+
+/**
+ * @file dac.h
+ * @author Afilias Technologies
+ *
+ * @brief API main header file
+ */
+
+#include <sys/types.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#ifndef __cplusplus
+#ifndef true
+#ifdef HAVE_NO_BUILTIN__BOOL
+typedef int _Bool;
+#endif
+#define bool _Bool
+
+#define true 1
+#define false 0
+#endif
+#endif
+
+#define MOBI_DA_MAJOR 2
+#define MOBI_DA_MINOR 1
+#define MOBI_DA_DUMMY_LIBRARY 1
+
+
+/**
+ * @brief All values returned by the API have one of these types.
+ * da_getprop*() return data in the appropriate C type for the given da_type.
+ */
+enum da_type {
+ DA_TYPE_NONE,
+ DA_TYPE_BOOLEAN,
+ DA_TYPE_INTEGER,
+ DA_TYPE_NUMBER,
+ DA_TYPE_STRING,
+ DA_TYPE_ARRAY,
+ DA_TYPE_OBJECT,
+ DA_TYPE_NULL
+};
+
+/**
+ * Any method that returns a da_status may potentially fail for one of these reasons.
+ * XXX: Error reporting needs to be improved.
+ */
+enum da_status {
+ DA_OK, /* Success. */
+ DA_INVALID_JSON, /* The JSON format is invalid, or the content is unexpected in a given context. */
+ DA_OVERFLOW, /* Overflow occurred. Note this is used to indicate an unfinished string parse in JSON */
+ DA_FORMAT_ERROR, /* The data supplied is formatted incorrectly. */
+ DA_NOMEM, /* There was not enough space to complete the operation */
+ DA_SYS, /* A system error occurred - consult the OS for more details (eg, check errno) */
+ DA_NOTIMPL, /* This method is not implemented */
+ DA_NOTFOUND, /* The requested item was not found. */
+ DA_REGEXBAD, /* An invalid regex was provided. */
+ DA_NOMORE, /* Used to indicate the end of an iterator. */
+ DA_INVALID_COOKIE, /* Cookie value supplied was invalid */
+ DA_INVALID_TYPE, /* A value of an unexpected type was found. */
+ DA_INTERNAL_ERROR,
+ DA_STATUS_LAST /* Placeholder to indicate highest possible error value. (value will change as API matures) */
+};
+
+enum da_severity {
+ DA_SEV_FATAL, /* The operation will not continue, and the operation will return an error. */
+ DA_SEV_ERROR, /* An error occurred, but the API call will return at least some valid information */
+ DA_SEV_WARN, /* An unexpected event occurred, but the system dealt with it */
+ DA_SEV_INFO /* An informational message. */
+};
+/* Forward references to tagged types */
+struct atlas_image;
+struct da_atlas;
+struct da_deviceinfo;
+struct da_jsonparser;
+struct da_node;
+struct da_propset;
+union da_value;
+struct da_evidence;
+struct da_bitset;
+struct da_allocator;
+struct da_config;
+
+/**
+ * @brief Primary types of the interface.
+ * Primary types used by API client.
+ * Non-typedef structures and unions are considered private to the API.
+ *
+ */
+typedef enum da_severity da_severity_t; /* A severity for the error callback. */
+typedef enum da_status da_status_t; /* An error code - returned from most API calls. */
+typedef da_status_t (*da_setpos_fn)(void *ctx, off_t off); /* callback provided to API to rewind input stream */
+typedef enum da_type da_type_t; /* A value type (integer, string, etc) */
+
+/**
+ * @brief An operation on an atlas involves converting a set of evidence strings into a set of property/value pairs.
+ * The ID for a particular type of evidence is extract from the atlas (eg, for a specific HTTP header, use:
+ *
+ * da_evidence_id_t evidence = da_atlas_header_evidence_id(atlas, "User-Agent");
+ *
+ */
+typedef int da_evidence_id_t;
+
+/**
+ * @brief The search result encompasses a key/value set. Keys are handles retrieved via
+ * _either_ da_atlas_getpropid() or da_getpropid().
+ * Some search results may have keys not available when the atlas is opened (eg,
+ * when the name of the property itself is contained within the evidence)
+ * Such properties by necessity are given a "local" da_propid_t
+ *
+ * You can ensure any properties you are interested in get a global propid by
+ * passing a list of interesting named properties to da_atlas_open()
+ */
+typedef int da_propid_t;
+typedef size_t (*da_read_fn)(void *ctx, size_t maxlen, char *ptr);
+typedef struct da_atlas da_atlas_t;
+typedef struct da_deviceinfo da_deviceinfo_t;
+typedef struct da_evidence da_evidence_t;
+typedef struct da_jsonparser da_jsonparser_t;
+typedef struct da_node da_node_t;
+typedef struct da_property_decl da_property_decl_t;
+typedef struct da_propset da_propset_t;
+typedef struct da_config da_config_t;
+typedef void *(*da_alloc_fn)(void *ctx, size_t);
+typedef void (*da_free_fn)(void *ctx, void *);
+typedef void *(*da_realloc_fn)(void *ctx, void *, size_t);
+typedef void (*da_errorfunc_t)(da_severity_t severity, da_status_t status, const char *msg, va_list args);
+
+
+/* Manifest constants. */
+enum {
+ /*
+ * used as the initial guess for the compiled size of an atlas.
+ * If atlas sizes grow more beyond this, it can be expanded to avoid multiple scans of the data.
+ */
+ DA_INITIAL_MEMORY_ESTIMATE = 1024 * 1024 * 14
+};
+
+struct da_config {
+ unsigned int ua_props;
+ unsigned int lang_props;
+ unsigned int __reserved[14]; /* enough reserved keywords for future use */
+};
+
+/**
+ * Functional interface.
+ */
+
+/**
+ * @brief Initialize process to use the DA API.
+ */
+void da_init(void);
+
+
+/**
+ * @brief Release all resources used by the API
+ */
+void da_fini(void);
+
+/**
+ * @brief User-supplied callback to be invoked with information about an error.
+ * Note this may use thread-local storage etc to store the info on return from the current call
+ * It is guaranteed that an error-reporting function returning an error-code will have called
+ * this function at least once.
+ * @param callback function
+ */
+void da_seterrorfunc(da_errorfunc_t callback);
+
+/**
+ * @brief Given a specific HTTP header, return the associated ID for that header.
+ * When passing evidence to the API, its type is identified using its da_evidince_id_t.
+ * @param atlas atlas instance
+ * @param header_name Header's name
+ * @return evidence id
+ */
+da_evidence_id_t da_atlas_header_evidence_id(const da_atlas_t *atlas, const char *header_name);
+/**
+ * @brief Return the associated ID of the client side properties evidence
+ * @param atlas Atlas instance
+ * @return evidence id
+ */
+da_evidence_id_t da_atlas_clientprop_evidence_id(const da_atlas_t *atlas);
+/**
+ * @brief Return the associated ID of the accept language header evidence
+ * @param atlas Atlas instance
+ * @return evidence id
+ */
+da_evidence_id_t da_atlas_accept_language_evidence_id(const da_atlas_t *atlas);
+
+/**
+ * @brief readfn should present JSON content from ctx.
+ * atlasp points to an uninitialized da_atlas structure.
+ * Result is a compiled atlas at atlasp.
+ * Result is allocated via normal memory-allocation methods, malloc/calloc/realloc, so should be
+ * Free'd with free()
+ * XXX TODO: Change this to take a da_allocator
+ * @param ctx pointer given to read the json file
+ * @param readfn function pointer, set accordingly to the attended given pointer
+ * @param setposfn function pointer
+ * @param ptr Pointer dynamically allocated if the json parsing happened normally
+ * @param len size of the atlas image
+ * @return status of atlas compilation
+ */
+da_status_t da_atlas_compile(void *ctx, da_read_fn readfn, da_setpos_fn setposfn, void **ptr, size_t *len);
+
+/**
+ * @brief opens a previously compiled atlas for operations. extra_props will be available in calls to
+ * da_getpropid on the atlas, and if generated by the search, the ID will be consistent across
+ * different calls to search.
+ * Properties added by a search that are neither in the compiled atlas, nor in the extra_props list
+ * Are assigned an ID within the context that is not transferrable through different search results
+ * within the same atlas.
+ * @param atlas Atlas instance
+ * @param extra_props properties
+ * @param ptr given pointer from previously compiled atlas
+ * @param pos atlas image size
+ * @return status of atlas data opening
+ */
+da_status_t da_atlas_open(da_atlas_t *atlas, da_property_decl_t *extra_props, const void *ptr, size_t pos);
+
+/**
+ * @brief read from a mapped data which then replace da_atlas_compile call
+ *
+ * @param dumppath, anonymous if NULL
+ * @param map for anonymous, it is the responsibility of the caller to unmap it, ignored otherwise
+ * @param maplen for anonymous, it is the size of the mapped data, ignored otherwise
+ * @param ptr Pointer dynamically allocated if the mapping happened normally
+ * @param len size of the atlas image
+ * @return status of mapping
+ */
+da_status_t da_atlas_read_mapped(const char *path, void *m, void **p, size_t *l);
+/**
+ * @brief Release any resources associated with the atlas structure atlas, which was previously generated from
+ * da_read_atlas or da_compile_atlas.
+ * @param atlas instance
+ */
+void da_atlas_close(da_atlas_t *atlas);
+
+/**
+ * @brief Find device properties given a set of evidence.
+ * Search results are returned in da_deviceinfo_t, and must be cleaned using da_close
+ * "Evidence" is an array of length count, of string data tagged with an evidence ID.
+ * @param atlas Atlas instance
+ * @param info Device info
+ * @param ev Array of evidences
+ * @param count Number of evidence given
+ * @return status of the search
+ */
+da_status_t da_searchv(const da_atlas_t *atlas, da_deviceinfo_t *info, da_evidence_t *ev, size_t count);
+
+/**
+ * @brief As da_search, but unrolls the evidence array into variable arguments for simpler calling
+ * convention with known evidence types.
+ * varargs are pairs of (da_evidence_id, string), terminated with da_evidence_id DA_END
+ * @code da_search(&myAtlas, &deviceInfo, da_get_header_evidence_id("User-Agent"),
+ * "Mozilla/5.0 (Linux...", DA_END);
+ * @endcode
+ * @param atlas Atlas instance
+ * @param info given device info which holds on device properties
+ * @param pairs of evidence id / evidence value
+ * @return status of the search
+ */
+da_status_t da_search(const da_atlas_t *atlas, da_deviceinfo_t *info, ...);
+
+/**
+ * @brief After finishing with a search result, release resources associated with it.
+ * @param info Device info previously allocated by search functions
+ */
+void da_close(da_deviceinfo_t *info);
+
+/**
+ * @brief Given a property name (Eg, "displayWidth"), return the property ID associated with it for the
+ * specified atlas.
+ * @param atlas Atlas instance
+ * @param propname Property name
+ * @param propid Property id
+ * @return status of the property id search
+ */
+da_status_t da_atlas_getpropid(const da_atlas_t *atlas, const char *propname, da_propid_t *propid);
+
+/**
+ * @brief Given a property ID, return the type of that property.
+ * @code
+ * da_getproptype(&myAtlas, da_getpropid(&myAtlas, "displayWidth"), &propertyType);
+ * assert(propertyType == DA_TYPE_INT);
+ * @endcode
+ * @param atlas Atlas instance
+ * @param propid Property id
+ * @param type Type id of the property
+ * @return status of the type id search
+ */
+da_status_t da_atlas_getproptype(const da_atlas_t *atlas, da_propid_t propid, da_type_t *type);
+
+/**
+ * @brief Given a property ID, return the name of that property.
+ * @code
+ * da_atlas_getpropname(&myAtlas, da_getpropid(&myAtlas, "displayWidth"), &propertyName);
+ * assert(strcmp("displayWidth", propertyName) == 0);
+ * @endcode
+ * @param atlas Atlas instance
+ * @param propid property id
+ * @param propname property name returned
+ * @return status of the property name search
+ */
+da_status_t da_atlas_getpropname(const da_atlas_t *atlas, da_propid_t propid, const char **propname);
+
+
+/**
+ * @brief Given an atlas instance, return its counters + the builtins
+ * @code
+ * da_atlas_getpropcount(&myAtlas);
+ * @endcode
+ * @param atlas Atlas instance
+ * @return counters
+ */
+size_t da_atlas_getpropcount(const da_atlas_t *atlas);
+
+/**
+ * @brief Given an atlas instance, set the detection config
+ * @param atlas Atlas instance
+ * @param config instance
+ */
+void da_atlas_setconfig(da_atlas_t *atlas, da_config_t *config);
+
+/**
+ * @brief Given a search result, find the value of a specific property.
+ * @code
+ * long displayWidth; // width of display in pixels.
+ * da_getpropinteger(&deviceInfo, da_getpropid(&myAtlas, "displayWidth"), &displayWidth);
+ * @endcode
+ * String contents are owned by the search result, and are valid until the search is closed.
+ */
+/**
+ * @brief returns a property value as a string from a given string typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropstring(const da_deviceinfo_t *info, da_propid_t propid, const char **value);
+/**
+ * @brief returns a property value as a long from a given long typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropinteger(const da_deviceinfo_t *info, da_propid_t propid, long *value);
+/**
+ * @brief returns a property value as a boolean from a given boolean typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropboolean(const da_deviceinfo_t *info, da_propid_t propid, bool *value);
+/**
+ * @brief returns a property value as a float from a given float typed property id
+ * @param info Device info
+ * @param propid Property id
+ * @param value Value of the property
+ * @return status of property value search
+ */
+da_status_t da_getpropfloat(const da_deviceinfo_t *info, da_propid_t propid, double *value);
+
+/**
+ * @brief Some properties may not be not known to the atlas before the search commences.
+ * Such properties cannot have a da_propid_t assigned to them on the atlas, but will
+ * have a local property assigned during search. The name and type of such properties
+ * can be discovered here.
+ *
+ * Properties that are used in the atlas source and properties specifically registered
+ * with da_atlas_open() will always be assigned to a property discovered during search.
+ * Therefore, if there are specific properties that you want to use, and are unsure
+ * if they are in your device atlas source, registering them with da_atlas_open will
+ * make access to them easier and more efficient
+ */
+/**
+ * @brief returns the type of a given device property from the search functions
+ * @param info Device info
+ * @param propid Property id
+ * @param type Type id
+ * @return status of property type search
+ */
+da_status_t da_getproptype(const da_deviceinfo_t *info, da_propid_t propid, da_type_t *type);
+/**
+ * @brief returns the name of a given device property from the search functions
+ * @param info Device info
+ * @param propid Property id
+ * @param propname Property name
+ * @return status of property type search
+ */
+da_status_t da_getpropname(const da_deviceinfo_t *info, da_propid_t propid, const char **propname);
+
+/**
+ * @brief da_getfirstprop/da_getnextprop provide iteration over all properties
+ * in a search result.
+ * Both will return DA_OK if there is a result available, and DA_NOMORE
+ * if the search is complete.
+ * @code
+ *
+ * da_propid_t *propidp;
+ * for (da_status_t status = da_getfirstprop(&result, &propidp);
+ * status == DA_OK;
+ * status = da_getnextprop(&result, &propidp)) {
+ * const char *propname;
+ * if (da_getpropname(&result, *propidp, &propname) == DA_OK)
+ * fprintf("found property %s\n", propname);
+ * }
+ * @endcode
+ */
+
+/**
+ * @brief returns the first property from device info
+ * @param info Device info
+ * @param propid Property
+ * @return status
+ */
+da_status_t da_getfirstprop(const da_deviceinfo_t *info, da_propid_t **propid);
+/**
+ * @brief device info properties iterator
+ * @param info Device info
+ * @param propid Property
+ * @return status
+ */
+da_status_t da_getnextprop(const da_deviceinfo_t *info, da_propid_t **propid);
+
+/**
+ * @brief Report an error, as per a report from the API to the user-callback.
+ * @param severity Severity level of the error
+ * @param fmt format error message
+ * @param va_list
+ * @return status
+ */
+da_status_t da_reporterror(da_status_t severity, const char *fmt, ...);
+
+/**
+ * @brief returns a textual description of the type "type".
+ * @param type Type id
+ * @return type name
+ */
+const char *da_typename(da_type_t type);
+
+/**
+ * @brief returns the version from the JSON in memory
+ * @param atlas
+ * @return version
+ */
+char *da_getdataversion(da_atlas_t *atlas);
+
+/**
+ * @brief returns the date creation's timestamp from the JSON in memory
+ * @param atlas
+ * @return version
+ */
+time_t da_getdatacreation(da_atlas_t *atlas);
+
+/**
+ * @brief returns the revision's number from the JSON in memory
+ * @param atlas
+ * @return version
+ */
+int da_getdatarevision(da_atlas_t *atlas);
+
+/**
+ * @brief returns the name of a global property
+ * @param atlas Atlas instance
+ * @param propid Property id
+ * @return property name
+ */
+const char *da_get_property_name(const da_atlas_t *atlas, da_propid_t propid);
+
+/**
+ * @brief returns the number of properties in a result.
+ * @param info Device info
+ * @return properties count
+ */
+size_t da_getpropcount(const da_deviceinfo_t *info);
+
+/*
+ * Details below should not be required for usage of the API
+ */
+
+/**
+ * @brief Represents a usable device atlas interface.
+ *
+ * No user servicable parts inside: access should
+ * be via the functional API.
+ */
+struct da_atlas {
+ const struct atlas_image *image;
+ struct header_evidence_entry *header_priorities;
+ size_t header_evidence_count;
+
+ struct pcre_regex_info *uar_regexes;
+ size_t uar_regex_count;
+
+ struct pcre_regex_info *replacement_regexes;
+ size_t replacement_regex_count;
+
+ da_evidence_id_t user_agent_evidence;
+ da_evidence_id_t clientprops_evidence;
+ da_evidence_id_t accept_language_evidence;
+ da_evidence_id_t next_evidence;
+
+ da_propset_t *properties;
+ da_propid_t id_propid;
+ da_propid_t id_proplang;
+ da_propid_t id_proplang_locale;
+
+ da_config_t config;
+
+ da_deviceinfo_t **cpr_props;
+ size_t cpr_count;
+};
+
+/* fixed constants. */
+enum {
+ DA_BUFSIZE = 16000
+};
+
+/**
+ * Represents a chunk of memory. See comments on da_deviceinfo.
+ * This is presented here to allow aggregation in da_deviceinfo:
+ * Not for public consumption.
+ */
+struct da_buf {
+ struct da_buf *next;
+ char *cur;
+ char *limit;
+ char buf[DA_BUFSIZE];
+};
+
+/**
+ * A callback interface for allocating memory from some source
+ * Not for public consumption.
+ */
+struct da_allocator {
+ da_alloc_fn alloc;
+ da_free_fn free;
+ da_realloc_fn realloc;
+ void *context;
+};
+
+
+/**
+ * Represents a search result
+ * Can be used to retrieve values of known properties discovered from the evidence,
+ * iterate over the properties with known values, and query property types that are
+ * local to this result.
+ *
+ * The atlas the search is carried out on must survive any da_deviceinfo results
+ * it provides.
+ */
+struct da_deviceinfo {
+ struct da_allocator allocator;
+ const da_atlas_t *atlas; /* reference to the atlas the search was carried out on. */
+ struct da_bitset *present; /* property received from tree */
+ struct da_bitset *localprop; /* property was received from UAR rule or CPR */
+ struct da_bitset *cprprop; /* property was received from CPR */
+ union da_value *properties; /* properties - indexed by property id. */
+ da_propid_t *proplist; /* list of properties present in this result. */
+ size_t propcount; /* size of proplist */
+ da_propset_t *local_types; /* property descriptors local to this search result. */
+
+ /**
+ * The per-deviceinfo heap is stored here. Allocations for data in the result
+ * come from the raw data in these buffers. The size of the fixed-size buffer
+ * built in to da_buf is sized such that all known search results will not
+ * require memory allocation via malloc()
+ */
+ struct da_buf *heap;
+ struct da_buf initial_heap;
+};
+
+/**
+ * Used to pass evidence to da_searchv()
+ */
+struct da_evidence {
+ da_evidence_id_t key;
+ char *value;
+};
+
+/**
+ * Used to pass properties the API intends to query to the da_atlas_open function
+ * This can be used to improve performance of lookup on properties well-known
+ * to the API user, but not present in the JSON database.
+ */
+struct da_property_decl {
+ const char *name;
+ da_type_t type;
+};
+
+
+#endif /* DEVATLAS_DAC_H */
diff --git a/addons/deviceatlas/dummy/dadwcom.c b/addons/deviceatlas/dummy/dadwcom.c
new file mode 100644
index 0000000..53c5fdf
--- /dev/null
+++ b/addons/deviceatlas/dummy/dadwcom.c
@@ -0,0 +1 @@
+#include <stdio.h>
diff --git a/addons/deviceatlas/dummy/dasch.c b/addons/deviceatlas/dummy/dasch.c
new file mode 100644
index 0000000..53c5fdf
--- /dev/null
+++ b/addons/deviceatlas/dummy/dasch.c
@@ -0,0 +1 @@
+#include <stdio.h>
diff --git a/addons/deviceatlas/dummy/json.c b/addons/deviceatlas/dummy/json.c
new file mode 100644
index 0000000..53c5fdf
--- /dev/null
+++ b/addons/deviceatlas/dummy/json.c
@@ -0,0 +1 @@
+#include <stdio.h>
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..2ee74d3
--- /dev/null
+++ b/addons/ot/Makefile
@@ -0,0 +1,75 @@
+# 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_USE_VARS : allows the use of variables for the OpenTracing context
+
+OT_DEFINE =
+OT_CFLAGS =
+OT_LDFLAGS =
+OT_DEBUG_EXT =
+OT_PKGSTAT =
+OTC_WRAPPER = opentracing-c-wrapper
+
+ifneq ($(OT_DEBUG),)
+OT_DEBUG_EXT = _dbg
+OT_DEFINE = -DDEBUG_OT
+endif
+
+ifeq ($(OT_INC),)
+OT_PKGSTAT = $(shell pkg-config --exists $(OTC_WRAPPER)$(OT_DEBUG_EXT); echo $$?)
+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_PKGSTAT),)
+ifeq ($(OT_CFLAGS),)
+$(error OpenTracing C wrapper : can't find headers)
+endif
+else
+ifneq ($(OT_PKGSTAT),0)
+$(error OpenTracing C wrapper : can't find package)
+endif
+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
+
+ifneq ($(OT_USE_VARS),)
+OT_DEFINE += -DUSE_OT_VARS
+OPTIONS_OBJS += \
+ addons/ot/src/vars.o
+endif
+
+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..a4bb7fc
--- /dev/null
+++ b/addons/ot/include/conf.h
@@ -0,0 +1,228 @@
+/***
+ * 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' %p '%s' %p %u %hhu %hhu 0x%02hhx %p:%s 0x%08x %s %s %s }", \
+ FLT_OT_CONF_HDR_ARGS(a, id), (a)->config, (a)->cfgbuf, (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 *cfgbuf; /* The OpenTracing configuration. */
+ 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..6274961
--- /dev/null
+++ b/addons/ot/include/debug.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_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_RETURN_EX(a,t,f) do { dbg_indent_level -= 3; { t _r = (a); FLT_OT_DBG(1, "} = " f, _r); return _r; } } while (0)
+# define FLT_OT_RETURN_INT(a) FLT_OT_RETURN_EX((a), int, "%d")
+# define FLT_OT_RETURN_PTR(a) FLT_OT_RETURN_EX((a), void *, "%p")
+# define FLT_OT_DBG_IFDEF(a,b) a
+# define FLT_OT_DBG_ARGS(a, ...) a, ##__VA_ARGS__
+# define FLT_OT_DBG_BUF(a,b) do { FLT_OT_DBG((a), "%p:{ %zu %p %zu %zu }", (b), (b)->size, (b)->area, (b)->data, (b)->head); } while (0)
+
+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_RETURN_EX(a,t,f) return a
+# define FLT_OT_RETURN_INT(a) return a
+# define FLT_OT_RETURN_PTR(a) return a
+# define FLT_OT_DBG_IFDEF(a,b) b
+# define FLT_OT_DBG_ARGS(...)
+# define FLT_OT_DBG_BUF(a,b) while (0)
+#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..3c3e4a3
--- /dev/null
+++ b/addons/ot/include/define.h
@@ -0,0 +1,107 @@
+/***
+ * 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_DELETE(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__); \
+ \
+ FLT_OT_DBG(3, "%d err: '%s'", __LINE__, *err); \
+ } \
+ } 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..c97a0cc
--- /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 FLT_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..f1a5672
--- /dev/null
+++ b/addons/ot/include/include.h
@@ -0,0 +1,66 @@
+/***
+ * 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/clock.h>
+#include <haproxy/filters.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/http_rules.h>
+#include <haproxy/log.h>
+#include <haproxy/proxy.h>
+#include <haproxy/sample.h>
+#include <haproxy/tcp_rules.h>
+#include <haproxy/tools.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..2b88a33
--- /dev/null
+++ b/addons/ot/include/opentracing.h
@@ -0,0 +1,86 @@
+/***
+ * 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 *plugin, char **err);
+int ot_start(struct otc_tracer *tracer, const char *cfgbuf, 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..53e414b
--- /dev/null
+++ b/addons/ot/include/parser.h
@@ -0,0 +1,172 @@
+/***
+ * 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_IGNORE_NAME '-'
+#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, CHAR, 2, 2, "ot-tracer", " <name>") \
+ FLT_OT_PARSE_TRACER_DEF( ACL, 0, CHAR, 3, 0, "acl", " <name> <criterion> [flags] [operator] <value> ...") \
+ FLT_OT_PARSE_TRACER_DEF( LOG, 0, CHAR, 2, 0, "log", " { global | <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]] }") \
+ FLT_OT_PARSE_TRACER_DEF( CONFIG, 0, NONE, 2, 2, "config", " <file>") \
+ FLT_OT_PARSE_TRACER_DEF( PLUGIN, 0, NONE, 2, 2, "plugin", " <file>") \
+ FLT_OT_PARSE_TRACER_DEF( GROUPS, 0, NONE, 2, 0, "groups", " <name> ...") \
+ FLT_OT_PARSE_TRACER_DEF( SCOPES, 0, NONE, 2, 0, "scopes", " <name> ...") \
+ FLT_OT_PARSE_TRACER_DEF( RATE_LIMIT, 0, NONE, 2, 2, "rate-limit", " <value>") \
+ FLT_OT_PARSE_TRACER_DEF( OPTION, 0, NONE, 2, 2, "option", " { disabled | dontlog-normal | hard-errors }") \
+ FLT_OT_PARSE_TRACER_DEF(DEBUG_LEVEL, 0, NONE, 2, 2, "debug-level", " <value>")
+
+#define FLT_OT_PARSE_GROUP_DEFINES \
+ FLT_OT_PARSE_GROUP_DEF( ID, 0, CHAR, 2, 2, "ot-group", " <name>") \
+ FLT_OT_PARSE_GROUP_DEF(SCOPES, 0, NONE, 2, 0, "scopes", " <name> ...")
+
+#ifdef USE_OT_VARS
+# define FLT_OT_PARSE_SCOPE_INJECT_HELP " <name-prefix> [use-vars] [use-headers]"
+# define FLT_OT_PARSE_SCOPE_EXTRACT_HELP " <name-prefix> [use-vars | use-headers]"
+#else
+# define FLT_OT_PARSE_SCOPE_INJECT_HELP " <name-prefix> [use-headers]"
+# define FLT_OT_PARSE_SCOPE_EXTRACT_HELP " <name-prefix> [use-headers]"
+#endif
+
+/*
+ * In case the possibility of working with OpenTracing context via HAProxyu
+ * variables is not used, args_max member of the structure flt_ot_parse_data
+ * should be reduced for 'inject' keyword. However, this is not critical
+ * because in this case the 'use-vars' argument cannot be entered anyway,
+ * so I will not complicate it here with additional definitions.
+ */
+#define FLT_OT_PARSE_SCOPE_DEFINES \
+ FLT_OT_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "ot-scope", " <name>") \
+ FLT_OT_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 5, "span", " <name> [<reference>] [root]") \
+ FLT_OT_PARSE_SCOPE_DEF( TAG, 1, NONE, 3, 0, "tag", " <name> <sample> ...") \
+ FLT_OT_PARSE_SCOPE_DEF( LOG, 1, NONE, 3, 0, "log", " <name> <sample> ...") \
+ FLT_OT_PARSE_SCOPE_DEF(BAGGAGE, 1, VAR, 3, 0, "baggage", " <name> <sample> ...") \
+ FLT_OT_PARSE_SCOPE_DEF( INJECT, 1, CTX, 2, 4, "inject", FLT_OT_PARSE_SCOPE_INJECT_HELP) \
+ FLT_OT_PARSE_SCOPE_DEF(EXTRACT, 0, CTX, 2, 3, "extract", FLT_OT_PARSE_SCOPE_EXTRACT_HELP) \
+ FLT_OT_PARSE_SCOPE_DEF( FINISH, 0, NONE, 2, 0, "finish", " <name> ...") \
+ FLT_OT_PARSE_SCOPE_DEF( ACL, 0, CHAR, 3, 0, "acl", " <name> <criterion> [flags] [operator] <value> ...") \
+ FLT_OT_PARSE_SCOPE_DEF( EVENT, 0, NONE, 2, 0, "event", " <name> [{ if | unless } <condition>]")
+
+enum FLT_OT_PARSE_INVCHAR_enum {
+ FLT_OT_PARSE_INVALID_NONE,
+ FLT_OT_PARSE_INVALID_CHAR,
+ FLT_OT_PARSE_INVALID_DOM,
+ FLT_OT_PARSE_INVALID_CTX,
+ FLT_OT_PARSE_INVALID_VAR,
+};
+
+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..7a3a776
--- /dev/null
+++ b/addons/ot/include/scope.h
@@ -0,0 +1,126 @@
+/***
+ * 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 '%s' %hhu %hhu 0x%02hhx 0x%08x %s %s }", \
+ (f), (a), (a)->stream, (a)->filter, (a)->uuid, (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. */
+};
+
+/* 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. */
+ char uuid[40]; /* 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..550cc89
--- /dev/null
+++ b/addons/ot/include/vars.h
@@ -0,0 +1,55 @@
+/***
+ * 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_CTX_SIZE int8_t
+#define FLT_OT_VAR_CHAR_DASH 'D'
+#define FLT_OT_VAR_CHAR_SPACE 'S'
+
+struct flt_ot_ctx {
+ char value[BUFSIZ];
+ int value_len;
+};
+
+typedef int (*flt_ot_ctx_loop_cb)(struct sample *, size_t, const char *, const char *, const char *, FLT_OT_VAR_CTX_SIZE, char **, void *);
+
+
+#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_vars_unset(struct stream *s, const char *scope, const char *prefix, 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..9132fe9
--- /dev/null
+++ b/addons/ot/src/cli.c
@@ -0,0 +1,397 @@
+/***
+ * 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)
+{
+ struct cli_print_ctx *ctx = applet_reserve_svcctx(appctx, sizeof(*ctx));
+
+ FLT_OT_FUNC("%p, %p, %p, %d", appctx, err, msg, cli_state);
+
+ if ((appctx == NULL) || ((err == NULL) && (msg == NULL)))
+ FLT_OT_RETURN();
+
+ ctx->err = (err == NULL) ? msg : err;
+ appctx->st0 = (ctx->err == NULL) ? CLI_ST_PROMPT : cli_state;
+
+ FLT_OT_DBG(1, "err(%d): \"%s\"", appctx->st0, ctx->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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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..d575e3a
--- /dev/null
+++ b/addons/ot/src/conf.c
@@ -0,0 +1,767 @@
+/***
+ * 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_PTR(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_APPEND(head, &(retptr->list));
+ } else {
+ FLT_OT_ERR("out of memory");
+ }
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(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_PTR(retptr);
+
+ flt_ot_args_to_str(args, 2, &(retptr->value));
+ if (retptr->value == NULL) {
+ FLT_OT_FREE_CLEAR(retptr);
+
+ FLT_OT_RETURN_PTR(retptr);
+ }
+
+ retptr->num_exprs = flt_ot_args_count(args) - 2;
+ LIST_INIT(&(retptr->exprs));
+
+ FLT_OT_DBG_CONF_SAMPLE("- init ", retptr);
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(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_PTR(retptr);
+
+ LIST_INIT(&(retptr->tags));
+ LIST_INIT(&(retptr->logs));
+ LIST_INIT(&(retptr->baggages));
+
+ FLT_OT_DBG_CONF_SPAN("- init ", retptr);
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(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_PTR(retptr);
+
+ LIST_INIT(&(retptr->ph_scopes));
+
+ FLT_OT_DBG_CONF_GROUP("- init ", retptr);
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(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)->cfgbuf);
+ 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_DELETE(&(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_PTR(retptr);
+
+ retptr->proxy = px;
+ LIST_INIT(&(retptr->groups));
+ LIST_INIT(&(retptr->scopes));
+
+ FLT_OT_DBG_CONF("- init ", retptr);
+
+ FLT_OT_RETURN_PTR(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..dc29d52
--- /dev/null
+++ b/addons/ot/src/event.c
@@ -0,0 +1,338 @@
+/***
+ * 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_INT(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++) {
+#ifdef USE_OT_VARS
+ 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;
+#endif
+
+ 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_INT(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_INT(retval);
+ }
+ }
+
+ list_for_each_entry(conf_ctx, &(conf_scope->contexts), list) {
+ struct otc_text_map *text_map = NULL;
+
+ 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);
+#ifdef USE_OT_VARS
+ else
+ text_map = flt_ot_vars_get(s, FLT_OT_VARS_SCOPE, conf_ctx->id, dir, err);
+#endif
+
+ 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_INT(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;
+ }
+
+#ifdef USE_OT_VARS
+ flt_ot_vars_dump(s);
+#endif
+ 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_INT(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..cf67fd2
--- /dev/null
+++ b/addons/ot/src/filter.c
@@ -0,0 +1,1176 @@
+/***
+ * 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_INT(retval);
+
+ flt_ot_cli_init();
+
+ /*
+ * Initialize the OpenTracing library.
+ */
+ retval = ot_init(&(conf->tracer->tracer), conf->tracer->plugin, &err);
+ if (retval != FLT_OT_RET_ERROR)
+ /* Do nothing. */;
+ else if (err != NULL) {
+ FLT_OT_ALERT("%s", err);
+
+ FLT_OT_ERR_FREE(err);
+ }
+
+ FLT_OT_RETURN_INT(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_INT(++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_INT(retval);
+}
+
+
+/***
+ * 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)
+{
+ 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_INT(retval);
+
+ /*
+ * Start the OpenTracing library tracer thread.
+ * Enable HTX streams filtering.
+ */
+ if (!(fconf->flags & FLT_CFG_FL_HTX)) {
+ retval = ot_start(conf->tracer->tracer, conf->tracer->cfgbuf, &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);
+ }
+ } else {
+ retval = FLT_OT_RET_OK;
+ }
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+#ifdef DEBUG_OT
+
+/***
+ * 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_INT(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_INT(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_INT(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);
+
+#ifdef USE_OT_VARS
+ flt_ot_vars_dump(s);
+#endif
+ flt_ot_http_headers_dump(&(s->req));
+
+ FLT_OT_RETURN_INT(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_INT(retval);
+
+ FLT_OT_RETURN_INT(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_INT(retval);
+
+ FLT_OT_DBG(3, "backend: %s", be->id);
+
+ FLT_OT_RETURN_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_init_per_thread,
+ .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..52b872d
--- /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_EX(ACT_RET_CONT, enum act_return, "%d");
+ }
+
+ if (conf->tracer->flag_disabled) {
+ FLT_OT_DBG(1, "filter '%s' disabled, group action '%s' ignored", conf->id, conf_group->id);
+
+ FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
+ }
+
+ /* 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_EX(ACT_RET_CONT, enum act_return, "%d");
+ }
+ else if (flt_ot_is_disabled(filter FLT_OT_DBG_ARGS(, -1))) {
+ FLT_OT_RETURN_EX(ACT_RET_CONT, enum act_return, "%d");
+ }
+ 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_EX(ACT_RET_CONT, enum act_return, "%d");
+ }
+
+ 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_EX(ACT_RET_CONT, enum act_return, "%d");
+}
+
+
+/***
+ * 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_INT(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_INT(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_INT(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_INT(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_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
+ }
+
+ /* 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_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
+ }
+
+ /* 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_EX(ACT_RET_PRS_ERR, enum act_parse_ret, "%d");
+ }
+
+ 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_EX(ACT_RET_PRS_OK, enum act_parse_ret, "%d");
+}
+
+
+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..517bd0d
--- /dev/null
+++ b/addons/ot/src/http.c
@@ -0,0 +1,312 @@
+/***
+ * 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_PTR(retptr);
+
+ /*
+ * The keyword 'inject' allows you to define the name of the OpenTracing
+ * context without using a prefix. In that case all HTTP headers are
+ * transferred because it is not possible to separate them from the
+ * OpenTracing context (this separation is usually done via a prefix).
+ *
+ * When using the 'extract' keyword, the context name must be specified.
+ * To allow all HTTP headers to be extracted, the first character of
+ * that name must be set to FLT_OT_PARSE_CTX_IGNORE_NAME.
+ */
+ if (FLT_OT_STR_ISVALID(prefix) && (*prefix == FLT_OT_PARSE_CTX_IGNORE_NAME))
+ prefix_len = 0;
+
+ 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);
+
+ /*
+ * In case the data of the HTTP header is not
+ * specified, v.ptr will have some non-null
+ * value and v.len will be equal to 0. The
+ * otc_text_map_add() function will not
+ * interpret this well. In this case v.ptr
+ * is set to an empty string.
+ */
+ if (v.len == 0)
+ v = ist("");
+
+ /*
+ * 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_PTR(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_INT(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_INT(retval);
+ }
+
+ if (!FLT_OT_STR_ISVALID(prefix)) {
+ ist_name = ist2((char *)name, strlen(name));
+ }
+ else if (!FLT_OT_STR_ISVALID(name)) {
+ ist_name = ist2((char *)prefix, strlen(prefix));
+ }
+ else {
+ buffer = flt_ot_trash_alloc(0, err);
+ if (buffer == NULL)
+ FLT_OT_RETURN_INT(retval);
+
+ (void)chunk_printf(buffer, "%s-%s", prefix, name);
+
+ ist_name = ist2(buffer->area, 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_INT(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_INT(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..8ae5f02
--- /dev/null
+++ b/addons/ot/src/opentracing.c
@@ -0,0 +1,1067 @@
+/***
+ * 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 __read_mostly = 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 -
+ * plugin -
+ * err -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+int ot_init(struct otc_tracer **tracer, const char *plugin, char **err)
+{
+ char cwd[PATH_MAX], path[PATH_MAX], errbuf[BUFSIZ] = "";
+ int rc, retval = -1;
+
+ FLT_OT_FUNC("%p:%p, \"%s\", %p:%p", FLT_OT_DPTR_ARGS(tracer), 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_INT(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_INT(retval);
+ }
+
+ *tracer = otc_tracer_load(path, 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_INT(retval);
+}
+
+
+/***
+ * NAME
+ * ot_start -
+ *
+ * ARGUMENTS
+ * tracer -
+ * cfgbuf -
+ * err -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * This function does not return a value.
+ */
+int ot_start(struct otc_tracer *tracer, const char *cfgbuf, char **err)
+{
+ char errbuf[BUFSIZ] = "";
+ int retval = -1;
+
+ FLT_OT_FUNC("%p, %p, %p:%p", tracer, cfgbuf, FLT_OT_DPTR_ARGS(err));
+
+ if (cfgbuf == NULL)
+ FLT_OT_RETURN_INT(retval);
+
+ retval = otc_tracer_start(NULL, cfgbuf, errbuf, sizeof(errbuf));
+ if (retval == -1)
+ FLT_OT_ERR("%s", (*errbuf == '\0') ? "failed to start tracer" : errbuf);
+
+ FLT_OT_RETURN_INT(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(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_INT(retval);
+
+ for (retval = 0; retval < num_tags; retval++)
+ span->set_tag(span, tags[retval].key, &(tags[retval].value));
+
+ FLT_OT_RETURN_INT(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_INT(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_INT(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_INT(retval);
+
+ retval = MIN(OTC_MAXLOGFIELDS, num_fields);
+
+ span->log_fields(span, log_fields, retval);
+
+ FLT_OT_RETURN_INT(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_INT(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_INT(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_INT(-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_INT(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_INT(retval);
+
+ if ((baggage->key == NULL) || (baggage->value == NULL))
+ FLT_OT_RETURN_INT(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_INT(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_INT(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_INT(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_PTR(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_PTR(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_PTR(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(retptr);
+
+ retptr = span->span_context((struct otc_span *)span);
+ if (retptr == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(retptr);
+
+ retptr = span->span_context((struct otc_span *)span);
+ if (retptr == NULL) {
+ FLT_OT_ERR("failed to create span context");
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(retptr);
+
+ retptr = span->span_context((struct otc_span *)span);
+ if (retptr == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(retptr);
+ else if (tracer == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(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..1fd7007
--- /dev/null
+++ b/addons/ot/src/parser.c
@@ -0,0 +1,1225 @@
+/***
+ * 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_INT(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_INT(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_EX(retptr, const char *, "%p");
+
+ if (type == FLT_OT_PARSE_INVALID_CHAR) {
+ retptr = invalid_char(name);
+ }
+ else if (type == FLT_OT_PARSE_INVALID_DOM) {
+ retptr = invalid_domainchar(name);
+ }
+ else if (type == FLT_OT_PARSE_INVALID_CTX) {
+ retptr = invalid_prefix_char(name);
+ }
+ else if (type == FLT_OT_PARSE_INVALID_VAR) {
+ 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_EX(retptr, const char *, "%p");
+}
+
+
+/***
+ * 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, argc, 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;
+
+ /* First check here if args[0] is the correct keyword. */
+ 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]);
+ else
+ argc = flt_ot_args_count(args);
+
+ 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))
+ if (argc < (*pdata)->args_min)
+ 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))
+ if (argc > (*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 != FLT_OT_PARSE_INVALID_NONE)) {
+ 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_INT(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_INT(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_INT(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_INT(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_INT(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, FLT_OT_PARSE_INVALID_##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_INT(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_INT(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, file, linenum, &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_INT(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)
+{
+ char errbuf[BUFSIZ] = "";
+ int retval = ERR_NONE;
+
+ FLT_OT_FUNC("");
+
+ if (flt_ot_current_tracer == NULL)
+ FLT_OT_RETURN_INT(retval);
+
+ flt_ot_current_config->tracer = flt_ot_current_tracer;
+
+ if (flt_ot_current_tracer->id == NULL)
+ FLT_OT_RETURN_INT(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);
+ } else {
+ flt_ot_current_tracer->cfgbuf = otc_file_read(flt_ot_current_tracer->config, "#", errbuf, sizeof(errbuf));
+ if (flt_ot_current_tracer->cfgbuf == NULL)
+ FLT_OT_POST_PARSE_ALERT("tracer '%s' %s", flt_ot_current_tracer->cfg_line, flt_ot_current_tracer->id, (*errbuf == '\0') ? "cannot load configuration file" : errbuf);
+ }
+
+ 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_INT(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, FLT_OT_PARSE_INVALID_##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_INT(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_INT(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_INT(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_INT(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_INT(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;
+#ifdef USE_OT_VARS
+ else if (strcmp(args[cur_arg], FLT_OT_PARSE_CTX_USE_VARS) == 0)
+ flags = FLT_OT_CTX_USE_VARS;
+#endif
+ 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_INT(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_PTR(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, FLT_OT_PARSE_INVALID_##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_INT(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_INT(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;
+#ifdef USE_OT_VARS
+ else if (strcmp(args[2], FLT_OT_PARSE_CTX_USE_VARS) == 0)
+ conf_ctx->flags = FLT_OT_CTX_USE_VARS;
+#endif
+ 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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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_INT(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..fbcdbfc
--- /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_PTR(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_PTR(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
+ * -
+ */
+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_PTR(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..efe8fe2
--- /dev/null
+++ b/addons/ot/src/scope.c
@@ -0,0 +1,634 @@
+/***
+ * 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 __read_mostly = NULL;
+static struct pool_head *pool_head_ot_scope_context __read_mostly = NULL;
+static struct pool_head *pool_head_ot_runtime_context __read_mostly = 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 buffer uuid;
+ 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_PTR(retptr);
+
+ retptr->stream = s;
+ retptr->filter = f;
+ 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));
+
+ uuid = b_make(retptr->uuid, sizeof(retptr->uuid), 0, 0);
+ ha_generate_uuid(&uuid);
+
+#ifdef USE_OT_VARS
+ /*
+ * The HAProxy variable 'sess.ot.uuid' is registered here,
+ * after which its value is set to runtime context UUID.
+ */
+ if (flt_ot_var_register(FLT_OT_VAR_UUID, err) != -1)
+ (void)flt_ot_var_set(s, FLT_OT_VAR_UUID, retptr->uuid, SMP_OPT_DIR_REQ, err);
+#endif
+
+ FLT_OT_DBG_RUNTIME_CONTEXT("session context: ", retptr);
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(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_PTR(retptr);
+ }
+ }
+ }
+
+ retptr = flt_ot_pool_alloc(pool_head_ot_scope_span, sizeof(*retptr), 1, err);
+ if (retptr == NULL)
+ FLT_OT_RETURN_PTR(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_INSERT(&(rt_ctx->spans), &(retptr->list));
+
+ FLT_OT_DBG_SCOPE_SPAN("new span ", retptr);
+
+ FLT_OT_RETURN_PTR(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_PTR(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_PTR(retptr);
+ }
+
+ retptr = flt_ot_pool_alloc(pool_head_ot_scope_context, sizeof(*retptr), 1, err);
+ if (retptr == NULL)
+ FLT_OT_RETURN_PTR(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_PTR(retptr);
+ }
+
+ retptr->id = id;
+ retptr->id_len = id_len;
+ retptr->smp_opt_dir = dir;
+ retptr->context = span_ctx;
+ LIST_INSERT(&(rt_ctx->contexts), &(retptr->list));
+
+ FLT_OT_DBG_SCOPE_CONTEXT("new context ", retptr);
+
+ FLT_OT_RETURN_PTR(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_INT(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);
+#ifdef USE_OT_VARS
+ (void)flt_ot_vars_unset(rt_ctx->stream, FLT_OT_VARS_SCOPE, ctx->id, ctx->smp_opt_dir, NULL);
+#endif
+
+ 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..767685e
--- /dev/null
+++ b/addons/ot/src/util.c
@@ -0,0 +1,815 @@
+/***
+ * 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, argc;
+
+ argc = flt_ot_args_count(args);
+
+ (void)fprintf(stderr, FLT_OT_DBG_FMT("%.*sargs[%d]: { '%s' "), dbg_indent_level, FLT_OT_DBG_INDENT, argc, args[0]);
+
+ for (i = 1; i < argc; 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_EX(-1, ssize_t, "%ld");
+
+ 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_EX(-1, ssize_t, "%ld");
+ }
+ else if (n > (chk->size - chk->data)) {
+ FLT_OT_ERR("chunk size too small");
+
+ FLT_OT_RETURN_EX(-1, ssize_t, "%ld");
+ }
+
+ (void)memcpy(chk->area + chk->data, src, n);
+ chk->data += n;
+
+ FLT_OT_RETURN_EX(chk->data, ssize_t, "%ld");
+}
+
+
+/***
+ * NAME
+ * flt_ot_args_count -
+ *
+ * ARGUMENTS
+ * args -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+int flt_ot_args_count(char **args)
+{
+ int i, retval = 0;
+
+ if (args == NULL)
+ return retval;
+
+ /*
+ * It is possible that some arguments within the configuration line
+ * are not specified; that is, they are set to a blank string.
+ *
+ * For example:
+ * keyword '' arg_2
+ *
+ * In that case the content of the args field will be like this:
+ * args[0]: 'keyword'
+ * args[1]: NULL pointer
+ * args[2]: 'arg_2'
+ * args[3 .. MAX_LINE_ARGS): NULL pointers
+ *
+ * The total number of arguments is the index of the last argument
+ * (increased by 1) that is not a NULL pointer.
+ */
+ for (i = 0; i < MAX_LINE_ARGS; i++)
+ if (FLT_OT_ARG_ISVALID(i))
+ retval = i + 1;
+
+ 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, argc;
+
+ if ((args == NULL) || (*args == NULL))
+ return;
+
+ argc = flt_ot_args_count(args);
+
+ for (i = idx; i < argc; i++)
+ (void)memprintf(str, "%s%s%s", (*str == NULL) ? "" : *str, (i == idx) ? "" : " ", (args[i] == NULL) ? "" : 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_INT(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_INT(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_INT(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_INT(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_INT(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..e99bab1
--- /dev/null
+++ b/addons/ot/src/vars.c
@@ -0,0 +1,834 @@
+/***
+ * 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
+ * Function prints the contents of all variables defined for a particular
+ * scope.
+ *
+ * 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;
+
+ vars_rdlock(vars);
+ list_for_each_entry(var, &(vars->head), l)
+ FLT_OT_DBG(2, "'%s.%016" PRIx64 "' -> '%.*s'", scope, var->name_hash, (int)b_data(&(var->data.u.str)), b_orig(&(var->data.u.str)));
+ vars_rdunlock(vars);
+}
+
+
+/***
+ * NAME
+ * flt_ot_vars_dump -
+ *
+ * ARGUMENTS
+ * s -
+ *
+ * DESCRIPTION
+ * Function prints the contents of all variables grouped by individual
+ * scope.
+ *
+ * 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(&(proc_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_smp_init -
+ *
+ * ARGUMENTS
+ * s -
+ * smp -
+ * opt -
+ * type -
+ * data -
+ *
+ * DESCRIPTION
+ * The function initializes the value of the 'smp' structure. If the 'data'
+ * argument is set, then the 'sample_data' member of the 'smp' structure is
+ * also initialized.
+ *
+ * RETURN VALUE
+ * This function does not return a value.
+ */
+static inline void flt_ot_smp_init(struct stream *s, struct sample *smp, uint opt, int type, const char *data)
+{
+ (void)memset(smp, 0, sizeof(*smp));
+ (void)smp_set_owner(smp, s->be, s->sess, s, opt | SMP_OPT_FINAL);
+
+ if (data != NULL) {
+ smp->data.type = type;
+
+ chunk_initstr(&(smp->data.u.str), data);
+ }
+}
+
+
+/***
+ * NAME
+ * flt_ot_smp_add -
+ *
+ * ARGUMENTS
+ * data -
+ * blk -
+ * len -
+ * err -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_smp_add(struct sample_data *data, const char *name, size_t len, char **err)
+{
+ bool flag_alloc = 0;
+ int retval = FLT_OT_RET_ERROR;
+
+ FLT_OT_FUNC("%p, \"%.*s\", %zu, %p:%p", data, (int)len, name, len, FLT_OT_DPTR_ARGS(err));
+
+ FLT_OT_DBG_BUF(2, &(data->u.str));
+
+ if (b_orig(&(data->u.str)) == NULL) {
+ data->type = SMP_T_BIN;
+ chunk_init(&(data->u.str), FLT_OT_MALLOC(global.tune.bufsize), global.tune.bufsize);
+
+ flag_alloc = (b_orig(&(data->u.str)) != NULL);
+ }
+
+ if (b_orig(&(data->u.str)) == NULL) {
+ FLT_OT_ERR("failed to add ctx '%.*s', not enough memory", (int)len, name);
+ }
+ else if (len > ((UINT64_C(1) << ((sizeof(FLT_OT_VAR_CTX_SIZE) << 3) - 1)) - 1)) {
+ FLT_OT_ERR("failed to add ctx '%.*s', too long name", (int)len, name);
+ }
+ else if ((len + sizeof(FLT_OT_VAR_CTX_SIZE)) > b_room(&(data->u.str))) {
+ FLT_OT_ERR("failed to add ctx '%.*s', too many names", (int)len, name);
+ }
+ else {
+ retval = b_data(&(data->u.str));
+
+ b_putchr(&(data->u.str), len);
+ (void)__b_putblk(&(data->u.str), name, len);
+
+ FLT_OT_DBG_BUF(2, &(data->u.str));
+ }
+
+ if ((retval == FLT_OT_RET_ERROR) && flag_alloc)
+ FLT_OT_FREE(b_orig(&(data->u.str)));
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_normalize_name -
+ *
+ * ARGUMENTS
+ * var_name -
+ * size -
+ * len -
+ * name -
+ * flag_cpy -
+ * err -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_normalize_name(char *var_name, size_t size, int *len, const char *name, bool flag_cpy, char **err)
+{
+ int retval = 0;
+
+ FLT_OT_FUNC("%p, %zu, %p, \"%s\", %hhu, %p:%p", var_name, size, len, name, flag_cpy, FLT_OT_DPTR_ARGS(err));
+
+ if (!FLT_OT_STR_ISVALID(name))
+ FLT_OT_RETURN_INT(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 {
+ FLT_OT_ERR("failed to normalize variable name, buffer too small");
+
+ retval = -1;
+ }
+
+ if (flag_cpy) {
+ /* Copy variable name without modification. */
+ retval = strlen(name);
+ if ((*len + retval + 1) > size) {
+ FLT_OT_ERR("failed to normalize variable name, buffer too small");
+
+ retval = -1;
+ } else {
+ (void)memcpy(var_name + *len, name, retval + 1);
+
+ *len += retval;
+ }
+ } else {
+ /*
+ * 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_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_var_name -
+ *
+ * ARGUMENTS
+ * scope -
+ * prefix -
+ * name -
+ * flag_cpy -
+ * var_name -
+ * size -
+ * err -
+ *
+ * DESCRIPTION
+ * The function initializes the value of the 'smp' structure. If the 'data'
+ * argument is set, then the 'sample_data' member of the 'smp' structure is
+ * also initialized.
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_var_name(const char *scope, const char *prefix, const char *name, bool flag_cpy, char *var_name, size_t size, char **err)
+{
+ int retval = 0;
+
+ FLT_OT_FUNC("\"%s\", \"%s\", \"%s\", %hhu, %p, %zu, %p:%p", scope, prefix, name, flag_cpy, var_name, size, FLT_OT_DPTR_ARGS(err));
+
+ if (flt_ot_normalize_name(var_name, size, &retval, scope, 0, err) >= 0)
+ if (flt_ot_normalize_name(var_name, size, &retval, prefix, 0, err) >= 0)
+ (void)flt_ot_normalize_name(var_name, size, &retval, name, flag_cpy, err);
+
+ if (retval == -1)
+ FLT_OT_ERR("failed to construct variable name '%s.%s.%s'", scope, prefix, name);
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_ctx_loop -
+ *
+ * ARGUMENTS
+ * smp -
+ * scope -
+ * prefix -
+ * err -
+ * func -
+ * ptr -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_ctx_loop(struct sample *smp, const char *scope, const char *prefix, char **err, flt_ot_ctx_loop_cb func, void *ptr)
+{
+ FLT_OT_VAR_CTX_SIZE var_ctx_size;
+ char var_name[BUFSIZ], var_ctx[BUFSIZ];
+ int i, var_name_len, var_ctx_len, rc, n = 1, retval = 0;
+
+ FLT_OT_FUNC("%p, \"%s\", \"%s\", %p:%p, %p, %p", smp, scope, prefix, FLT_OT_DPTR_ARGS(err), func, ptr);
+
+ /*
+ * The variable in which we will save the name of the OpenTracing
+ * context variable.
+ */
+ var_name_len = flt_ot_var_name(scope, prefix, NULL, 0, var_name, sizeof(var_name), err);
+ if (var_name_len == -1)
+ FLT_OT_RETURN_INT(FLT_OT_RET_ERROR);
+
+ /*
+ * Here we will try to find all the previously recorded variables from
+ * the currently set OpenTracing context. If we find the required
+ * variable and it is marked as deleted, we will mark it as active.
+ * If we do not find it, then it is added to the end of the previously
+ * saved names.
+ */
+ if (vars_get_by_name(var_name, var_name_len, smp, NULL) == 0) {
+ FLT_OT_DBG(2, "ctx '%s' no variable found", var_name);
+ }
+ else if (smp->data.type != SMP_T_BIN) {
+ FLT_OT_ERR("ctx '%s' invalid data type %d", var_name, smp->data.type);
+
+ retval = FLT_OT_RET_ERROR;
+ }
+ else {
+ FLT_OT_DBG_BUF(2, &(smp->data.u.str));
+
+ for (i = 0; i < b_data(&(smp->data.u.str)); i += sizeof(var_ctx_size) + var_ctx_len, n++) {
+ var_ctx_size = *((typeof(var_ctx_size) *)(b_orig(&(smp->data.u.str)) + i));
+ var_ctx_len = abs(var_ctx_size);
+
+ if ((i + sizeof(var_ctx_size) + var_ctx_len) > b_data(&(smp->data.u.str))) {
+ FLT_OT_ERR("ctx '%s' invalid data size", var_name);
+
+ retval = FLT_OT_RET_ERROR;
+
+ break;
+ }
+
+ (void)memcpy(var_ctx, b_orig(&(smp->data.u.str)) + i + sizeof(var_ctx_size), var_ctx_len);
+ var_ctx[var_ctx_len] = '\0';
+
+ rc = func(smp, i, scope, prefix, var_ctx, var_ctx_size, err, ptr);
+ if (rc == FLT_OT_RET_ERROR) {
+ retval = FLT_OT_RET_ERROR;
+
+ break;
+ }
+ else if (rc > 0) {
+ retval = n;
+
+ break;
+ }
+ }
+ }
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_ctx_set_cb -
+ *
+ * ARGUMENTS
+ * smp -
+ * idx -
+ * scope -
+ * prefix -
+ * name -
+ * name_len -
+ * err -
+ * ptr -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_ctx_set_cb(struct sample *smp, size_t idx, const char *scope, const char *prefix, const char *name, FLT_OT_VAR_CTX_SIZE name_len, char **err, void *ptr)
+{
+ struct flt_ot_ctx *ctx = ptr;
+ int retval = 0;
+
+ FLT_OT_FUNC("%p, %zu, \"%s\", \"%s\", \"%s\", %hhd, %p:%p, %p", smp, idx, scope, prefix, name, name_len, FLT_OT_DPTR_ARGS(err), ptr);
+
+ if ((name_len == ctx->value_len) && (strncmp(name, ctx->value, name_len) == 0)) {
+ FLT_OT_DBG(2, "ctx '%s' found\n", name);
+
+ retval = 1;
+ }
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_ctx_set -
+ *
+ * ARGUMENTS
+ * s -
+ * scope -
+ * prefix -
+ * name -
+ * opt -
+ * err -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_ctx_set(struct stream *s, const char *scope, const char *prefix, const char *name, uint opt, char **err)
+{
+ struct flt_ot_ctx ctx;
+ struct sample smp_ctx;
+ char var_name[BUFSIZ];
+ bool flag_alloc = 0;
+ int rc, var_name_len, retval = FLT_OT_RET_ERROR;
+
+ FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, name, opt, FLT_OT_DPTR_ARGS(err));
+
+ /*
+ * The variable in which we will save the name of the OpenTracing
+ * context variable.
+ */
+ var_name_len = flt_ot_var_name(scope, prefix, NULL, 0, var_name, sizeof(var_name), err);
+ if (var_name_len == -1)
+ FLT_OT_RETURN_INT(retval);
+
+ /* Normalized name of the OpenTracing context variable. */
+ ctx.value_len = flt_ot_var_name(name, NULL, NULL, 0, ctx.value, sizeof(ctx.value), err);
+ if (ctx.value_len == -1)
+ FLT_OT_RETURN_INT(retval);
+
+ flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
+
+ retval = flt_ot_ctx_loop(&smp_ctx, scope, prefix, err, flt_ot_ctx_set_cb, &ctx);
+ if (retval == 0) {
+ rc = flt_ot_smp_add(&(smp_ctx.data), ctx.value, ctx.value_len, err);
+ if (rc == FLT_OT_RET_ERROR)
+ retval = FLT_OT_RET_ERROR;
+
+ flag_alloc = (rc == 0);
+ }
+
+ if (retval == FLT_OT_RET_ERROR) {
+ /* Do nothing. */
+ }
+ else if (retval > 0) {
+ FLT_OT_DBG(2, "ctx '%s' data found", ctx.value);
+ }
+ else if (vars_set_by_name_ifexist(var_name, var_name_len, &smp_ctx) == 0) {
+ FLT_OT_ERR("failed to set ctx '%s'", var_name);
+
+ retval = FLT_OT_RET_ERROR;
+ }
+ else {
+ FLT_OT_DBG(2, "ctx '%s' -> '%.*s' set", var_name, (int)b_data(&(smp_ctx.data.u.str)), b_orig(&(smp_ctx.data.u.str)));
+
+ retval = b_data(&(smp_ctx.data.u.str));
+ }
+
+ if (flag_alloc)
+ FLT_OT_FREE(b_orig(&(smp_ctx.data.u.str)));
+
+ FLT_OT_RETURN_INT(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 = -1, var_name_len;
+
+ FLT_OT_FUNC("\"%s\", \"%s\", \"%s\", %p:%p", scope, prefix, name, FLT_OT_DPTR_ARGS(err));
+
+ var_name_len = flt_ot_var_name(scope, prefix, name, 0, var_name, sizeof(var_name), err);
+ if (var_name_len == -1)
+ FLT_OT_RETURN_INT(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 = var_name_len;
+
+ if (vars_check_arg(&arg, err) == 0) {
+ FLT_OT_ERR_APPEND("failed to register variable '%s': %s", var_name, *err);
+ } else {
+ FLT_OT_DBG(2, "variable '%s' registered", var_name);
+
+ retval = var_name_len;
+ }
+
+ FLT_OT_RETURN_INT(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 = -1, var_name_len;
+
+ FLT_OT_FUNC("%p, \"%s\", \"%s\", \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, name, value, opt, FLT_OT_DPTR_ARGS(err));
+
+ var_name_len = flt_ot_var_name(scope, prefix, name, 0, var_name, sizeof(var_name), err);
+ if (var_name_len == -1)
+ FLT_OT_RETURN_INT(retval);
+
+ flt_ot_smp_init(s, &smp, opt, SMP_T_STR, value);
+
+ if (vars_set_by_name_ifexist(var_name, var_name_len, &smp) == 0) {
+ FLT_OT_ERR("failed to set variable '%s'", var_name);
+ } else {
+ FLT_OT_DBG(2, "variable '%s' set", var_name);
+
+ retval = var_name_len;
+
+ if (strcmp(scope, FLT_OT_VARS_SCOPE) == 0)
+ retval = flt_ot_ctx_set(s, scope, prefix, name, opt, err);
+ }
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_vars_unset_cb -
+ *
+ * ARGUMENTS
+ * smp -
+ * idx -
+ * scope -
+ * prefix -
+ * name -
+ * name_len -
+ * err -
+ * ptr -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_vars_unset_cb(struct sample *smp, size_t idx, const char *scope, const char *prefix, const char *name, FLT_OT_VAR_CTX_SIZE name_len, char **err, void *ptr)
+{
+ struct sample smp_ctx;
+ char var_ctx[BUFSIZ];
+ int var_ctx_len, retval = FLT_OT_RET_ERROR;
+
+ FLT_OT_FUNC("%p, %zu, \"%s\", \"%s\", \"%s\", %hhd, %p:%p, %p", smp, idx, scope, prefix, name, name_len, FLT_OT_DPTR_ARGS(err), ptr);
+
+ var_ctx_len = flt_ot_var_name(scope, prefix, name, 1, var_ctx, sizeof(var_ctx), err);
+ if (var_ctx_len == -1) {
+ FLT_OT_ERR("ctx '%s' invalid", name);
+
+ FLT_OT_RETURN_INT(retval);
+ }
+
+ flt_ot_smp_init(smp->strm, &smp_ctx, smp->opt, 0, NULL);
+
+ if (vars_unset_by_name_ifexist(var_ctx, var_ctx_len, &smp_ctx) == 0) {
+ FLT_OT_ERR("ctx '%s' no variable found", var_ctx);
+ } else {
+ FLT_OT_DBG(2, "ctx '%s' unset", var_ctx);
+
+ retval = 0;
+ }
+
+ FLT_OT_RETURN_INT(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_ctx;
+ char var_name[BUFSIZ];
+ int var_name_len, retval;
+
+ FLT_OT_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, opt, FLT_OT_DPTR_ARGS(err));
+
+ flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
+
+ retval = flt_ot_ctx_loop(&smp_ctx, scope, prefix, err, flt_ot_vars_unset_cb, NULL);
+ if (retval != FLT_OT_RET_ERROR) {
+ /*
+ * After all ctx variables have been unset, the variable used
+ * to store their names should also be unset.
+ */
+ var_name_len = flt_ot_var_name(scope, prefix, NULL, 0, var_name, sizeof(var_name), err);
+ if (var_name_len == -1)
+ FLT_OT_RETURN_INT(FLT_OT_RET_ERROR);
+
+ flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
+
+ if (vars_unset_by_name_ifexist(var_name, var_name_len, &smp_ctx) == 0) {
+ FLT_OT_DBG(2, "variable '%s' not found", var_name);
+ } else {
+ FLT_OT_DBG(2, "variable '%s' unset", var_name);
+
+ retval = 1;
+ }
+ }
+
+ FLT_OT_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ * flt_ot_vars_get_cb -
+ *
+ * ARGUMENTS
+ * smp -
+ * idx -
+ * scope -
+ * prefix -
+ * name -
+ * name_len -
+ * err -
+ * ptr -
+ *
+ * DESCRIPTION
+ * -
+ *
+ * RETURN VALUE
+ * -
+ */
+static int flt_ot_vars_get_cb(struct sample *smp, size_t idx, const char *scope, const char *prefix, const char *name, FLT_OT_VAR_CTX_SIZE name_len, char **err, void *ptr)
+{
+ struct otc_text_map **map = ptr;
+ struct sample smp_ctx;
+ char var_ctx[BUFSIZ], ot_var_name[BUFSIZ], ch;
+ int var_ctx_len, ot_var_name_len, retval = FLT_OT_RET_ERROR;
+
+ FLT_OT_FUNC("%p, %zu, \"%s\", \"%s\", \"%s\", %hhd, %p:%p, %p", smp, idx, scope, prefix, name, name_len, FLT_OT_DPTR_ARGS(err), ptr);
+
+ var_ctx_len = flt_ot_var_name(scope, prefix, name, 1, var_ctx, sizeof(var_ctx), err);
+ if (var_ctx_len == -1) {
+ FLT_OT_ERR("ctx '%s' invalid", name);
+
+ FLT_OT_RETURN_INT(retval);
+ }
+
+ flt_ot_smp_init(smp->strm, &smp_ctx, smp->opt, 0, NULL);
+
+ if (vars_get_by_name(var_ctx, var_ctx_len, &smp_ctx, NULL) != 0) {
+ FLT_OT_DBG(2, "'%s' -> '%.*s'", var_ctx, (int)b_data(&(smp_ctx.data.u.str)), b_orig(&(smp_ctx.data.u.str)));
+
+ if (*map == NULL) {
+ *map = otc_text_map_new(NULL, 8);
+ if (*map == NULL) {
+ FLT_OT_ERR("failed to create map data");
+
+ FLT_OT_RETURN_INT(FLT_OT_RET_ERROR);
+ }
+ }
+
+ /*
+ * 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 (ot_var_name_len = 0; (ch = name[ot_var_name_len]) != '\0'; ot_var_name_len++)
+ if (ot_var_name_len >= (FLT_OT_TABLESIZE(ot_var_name) - 1)) {
+ FLT_OT_ERR("failed to reverse variable name, buffer too small");
+
+ otc_text_map_destroy(map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+
+ break;
+ } else {
+ ot_var_name[ot_var_name_len] = (ch == FLT_OT_VAR_CHAR_DASH) ? '-' : ((ch == FLT_OT_VAR_CHAR_SPACE) ? ' ' : ch);
+ }
+ ot_var_name[ot_var_name_len] = '\0';
+
+ if (*map == NULL) {
+ retval = FLT_OT_RET_ERROR;
+ }
+ else if (otc_text_map_add(*map, ot_var_name, ot_var_name_len, b_orig(&(smp_ctx.data.u.str)), b_data(&(smp_ctx.data.u.str)), OTC_TEXT_MAP_DUP_KEY | OTC_TEXT_MAP_DUP_VALUE) == -1) {
+ FLT_OT_ERR("failed to add map data");
+
+ otc_text_map_destroy(map, OTC_TEXT_MAP_FREE_KEY | OTC_TEXT_MAP_FREE_VALUE);
+
+ retval = FLT_OT_RET_ERROR;
+ }
+ else {
+ retval = 0;
+ }
+ } else {
+ FLT_OT_DBG(2, "ctx '%s' no variable found", var_ctx);
+ }
+
+ FLT_OT_RETURN_INT(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 sample smp_ctx;
+ struct otc_text_map *retptr = NULL;
+
+ FLT_OT_FUNC("%p, \"%s\", \"%s\", %u, %p:%p", s, scope, prefix, opt, FLT_OT_DPTR_ARGS(err));
+
+ flt_ot_smp_init(s, &smp_ctx, opt, 0, NULL);
+
+ (void)flt_ot_ctx_loop(&smp_ctx, scope, prefix, err, flt_ot_vars_get_cb, &retptr);
+
+ 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_PTR(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..8e678b7
--- /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..bfac617
--- /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..68b250c
--- /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..04a303a
--- /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..f2ac514
--- /dev/null
+++ b/addons/ot/test/test-speed.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+#
+ _ARG_CFG="${1}"
+ _ARG_DIR="${2:-${1}}"
+ _LOG_DIR="_logs"
+_HTTPD_PIDFILE="${_LOG_DIR}/thttpd.pid"
+ _USAGE_MSG="usage: $(basename "${0}") cfg [dir]"
+
+
+sh_exit ()
+{
+ test -z "${2}" && {
+ echo
+ echo "Script killed!"
+ }
+
+ test -n "${1}" && {
+ echo
+ echo "${1}"
+ echo
+ }
+
+ exit ${2:-64}
+}
+
+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
+}
+
+
+command -v thttpd >/dev/null 2>&1 || sh_exit "thttpd: command not found" 5
+command -v wrk >/dev/null 2>&1 || sh_exit "wrk: command not found" 6
+
+mkdir -p "${_LOG_DIR}" || sh_exit "${_LOG_DIR}: Cannot create log directory" 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 -z "${_ARG_CFG}" -o -z "${_ARG_DIR}" && sh_exit "${_USAGE_MSG}" 4
+test -f "run-${_ARG_CFG}.sh" || sh_exit "run-${_ARG_CFG}.sh: No such configuration script" 2
+test -d "${_ARG_DIR}" || sh_exit "${_ARG_DIR}: No such directory" 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
diff --git a/addons/promex/README b/addons/promex/README
new file mode 100644
index 0000000..4e29e23
--- /dev/null
+++ b/addons/promex/README
@@ -0,0 +1,356 @@
+PROMEX: A Prometheus exporter for HAProxy
+-------------------------------------------
+
+Prometheus is a monitoring and alerting system. More and more people use it to
+monitor their environment (this is written February 2019). It collects metrics
+from monitored targets by scraping metrics HTTP endpoints on these targets. For
+HAProxy, The Prometheus team officially supports an exporter written in Go
+(https://github.com/prometheus/haproxy_exporter). But it requires an extra
+software to deploy and monitor. PROMEX, on its side, is a built-in Prometheus
+exporter for HAProxy. It was developed as a service and is directly available in
+HAProxy, like the stats applet.
+
+However, PROMEX is not built by default with HAProxy. It is provided as an extra
+component for everyone want to use it. So you need to explicitly build HAProxy
+with the PROMEX service, setting the Makefile variable "USE_PROMEX" to "1". For
+instance:
+
+ > make TARGET=linux-glibc USE_PROMEX=1
+
+if HAProxy provides the PROMEX service, the following build option will be
+reported by the command "haproxy -vv":
+
+ Built with the Prometheus exporter as a service
+
+To be used, it must be enabled in the configuration with an "http-request" rule
+and the corresponding HTTP proxy must enable the HTX support. For instance:
+
+ frontend test
+ mode http
+ ...
+ http-request use-service prometheus-exporter if { path /metrics }
+ ...
+
+
+This service has been developed as a third-party component because it could
+become obsolete, depending on how much time Prometheus will remain heavily
+used. This is said with no ulterior motive of course. Prometheus is a great
+software and I hope all the well for it. But we involve in a environment moving
+quickly and a solution may be obvious today could be deprecated the next
+year. And because PROMEX is not integrated by default into the HAProxy codebase,
+it will need some interest to be actively supported. All contribution of any
+kind are welcome.
+
+You must also be careful if you use with huge configurations. Unlike the stats
+applet, all metrics are not grouped by service (proxy, listener or server). With
+PROMEX, all lines for a given metric are provided as one single group. So
+instead of collecting all metrics for a proxy before moving to the next one, we
+must loop on all proxies for each metric. Same for the servers. Thus, it will
+spend much more resources to produce the Prometheus metrics than the CSV export
+through the stats page. To give a comparison order, quick benchmarks shown that
+a PROMEX dump is 5x slower and 20x more verbose than a CSV export.
+
+
+metrics filtering
+-------------------
+
+It is possible to dynamically select the metrics to export if you don't use all
+of them passing parameters in the query-string.
+
+* Filtering on scopes
+
+The metrics may be filtered by scopes. Multiple parameters with "scope" as name
+may be passed in the query-string to filter exported metrics, with one of those
+values: global, frontend, backend, server or '*' (means all). A scope parameter
+with no value means to filter out all scopes (nothing is returned). The scope
+parameters are parsed in their appearance order in the query-string. So an empty
+scope will reset all scopes already parsed. But it can be overridden by
+following scope parameters in the query-string. By default everything is
+exported. Here are examples:
+
+ /metrics?scope=server # ==> server metrics will be exported
+ /metrics?scope=frontend&scope=backend # ==> Frontend and backend metrics will be exported
+ /metrics?scope=listener # ==> listener metrics will be exported
+ /metrics?scope=*&scope= # ==> no metrics will be exported
+ /metrics?scope=&scope=global # ==> global metrics will be exported
+ /metrics?scope=sticktable # ==> stick tables metrics will be exported
+
+* How do I prevent my prometheus instance to explode?
+
+** Filtering on servers state
+
+It is possible to exclude from returned metrics all servers in maintenance mode
+passing the parameter "no-maint" in the query-string. This parameter may help to
+solve performance issues of configuration that use the server templates to
+manage dynamic provisionning. Note there is no consistency check on the servers
+state. So, if the state of a server changes while the exporter is running, only
+a part of the metrics for this server will be dumped.
+
+prometheus example config:
+
+For server-template users:
+- <job>
+ params:
+ no-maint:
+ - empty
+
+** Scrap server health checks only
+
+All health checks status are dump through `state` label values. If you want to
+scrap server health check status but prevent all server metrics to be saved,
+except the server_check_status, you may configure prometheus that way:
+
+- <job>
+ metric_relabel_configs:
+ - source_labels: ['__name__']
+ regex: 'haproxy_(process_|frontend_|listener_|backend_|server_check_status).*'
+ action: keep
+
+Exported metrics
+------------------
+
+See prometheus export for the description of each field.
+
+* Globals metrics
+
++------------------------------------------------+
+| Metric name |
++------------------------------------------------+
+| haproxy_process_nbthread |
+| haproxy_process_nbproc |
+| haproxy_process_relative_process_id |
+| haproxy_process_uptime_seconds |
+| haproxy_process_pool_failures_total |
+| haproxy_process_max_fds |
+| haproxy_process_max_sockets |
+| haproxy_process_max_connections |
+| haproxy_process_hard_max_connections |
+| haproxy_process_current_connections |
+| haproxy_process_connections_total |
+| haproxy_process_requests_total |
+| haproxy_process_max_ssl_connections |
+| haproxy_process_current_ssl_connections |
+| haproxy_process_ssl_connections_total |
+| haproxy_process_max_pipes |
+| haproxy_process_pipes_used_total |
+| haproxy_process_pipes_free_total |
+| haproxy_process_current_connection_rate |
+| haproxy_process_limit_connection_rate |
+| haproxy_process_max_connection_rate |
+| haproxy_process_current_session_rate |
+| haproxy_process_limit_session_rate |
+| haproxy_process_max_session_rate |
+| haproxy_process_current_ssl_rate |
+| haproxy_process_limit_ssl_rate |
+| haproxy_process_max_ssl_rate |
+| haproxy_process_current_frontend_ssl_key_rate |
+| haproxy_process_max_frontend_ssl_key_rate |
+| haproxy_process_frontend_ssl_reuse |
+| haproxy_process_current_backend_ssl_key_rate |
+| haproxy_process_max_backend_ssl_key_rate |
+| haproxy_process_ssl_cache_lookups_total |
+| haproxy_process_ssl_cache_misses_total |
+| haproxy_process_http_comp_bytes_in_total |
+| haproxy_process_http_comp_bytes_out_total |
+| haproxy_process_limit_http_comp |
+| haproxy_process_current_zlib_memory |
+| haproxy_process_max_zlib_memory |
+| haproxy_process_current_tasks |
+| haproxy_process_current_run_queue |
+| haproxy_process_idle_time_percent |
+| haproxy_process_stopping |
+| haproxy_process_jobs |
+| haproxy_process_unstoppable_jobs |
+| haproxy_process_listeners |
+| haproxy_process_active_peers |
+| haproxy_process_connected_peers |
+| haproxy_process_dropped_logs_total |
+| haproxy_process_busy_polling_enabled |
+| haproxy_process_failed_resolutions |
+| haproxy_process_bytes_out_total |
+| haproxy_process_spliced_bytes_out_total |
+| haproxy_process_bytes_out_rate |
+| haproxy_process_recv_logs_total |
+| haproxy_process_build_info |
+| haproxy_process_max_memory_bytes |
+| haproxy_process_pool_allocated_bytes |
+| haproxy_process_pool_used_bytes |
+| haproxy_process_start_time_seconds |
++------------------------------------------------+
+
+* Frontend metrics
+
++-------------------------------------------------+
+| Metric name |
++-------------------------------------------------+
+| haproxy_frontend_current_sessions |
+| haproxy_frontend_max_sessions |
+| haproxy_frontend_limit_sessions |
+| haproxy_frontend_sessions_total |
+| haproxy_frontend_bytes_in_total |
+| haproxy_frontend_bytes_out_total |
+| haproxy_frontend_requests_denied_total |
+| haproxy_frontend_responses_denied_total |
+| haproxy_frontend_request_errors_total |
+| haproxy_frontend_status |
+| haproxy_frontend_limit_session_rate |
+| haproxy_frontend_max_session_rate |
+| haproxy_frontend_http_responses_total |
+| haproxy_frontend_http_requests_rate_max |
+| haproxy_frontend_http_requests_total |
+| haproxy_frontend_http_comp_bytes_in_total |
+| haproxy_frontend_http_comp_bytes_out_total |
+| haproxy_frontend_http_comp_bytes_bypassed_total |
+| haproxy_frontend_http_comp_responses_total |
+| haproxy_frontend_connections_rate_max |
+| haproxy_frontend_connections_total |
+| haproxy_frontend_intercepted_requests_total |
+| haproxy_frontend_denied_connections_total |
+| haproxy_frontend_denied_sessions_total |
+| haproxy_frontend_failed_header_rewriting_total |
+| haproxy_frontend_http_cache_lookups_total |
+| haproxy_frontend_http_cache_hits_total |
+| haproxy_frontend_internal_errors_total |
++-------------------------------------------------+
+
+* Listener metrics
+
++-------------------------------------------------+
+| Metric name |
++-------------------------------------------------+
+| haproxy_listener_current_sessions |
+| haproxy_listener_max_sessions |
+| haproxy_listener_limit_sessions |
+| haproxy_listener_sessions_total |
+| haproxy_listener_bytes_in_total |
+| haproxy_listener_bytes_out_total |
+| haproxy_listener_requests_denied_total |
+| haproxy_listener_responses_denied_total |
+| haproxy_listener_request_errors_total |
+| haproxy_listener_status |
+| haproxy_listener_denied_connections_total |
+| haproxy_listener_denied_sessions_total |
+| haproxy_listener_failed_header_rewriting_total |
+| haproxy_listener_internal_errors_total |
++-------------------------------------------------+
+
+* Backend metrics
+
++-----------------------------------------------------+
+| Metric name |
++-----------------------------------------------------+
+| haproxy_backend_current_queue |
+| haproxy_backend_max_queue |
+| haproxy_backend_current_sessions |
+| haproxy_backend_max_sessions |
+| haproxy_backend_limit_sessions |
+| haproxy_backend_sessions_total |
+| haproxy_backend_bytes_in_total |
+| haproxy_backend_bytes_out_total |
+| haproxy_backend_requests_denied_total |
+| haproxy_backend_responses_denied_total |
+| haproxy_backend_connection_errors_total |
+| haproxy_backend_response_errors_total |
+| haproxy_backend_retry_warnings_total |
+| haproxy_backend_redispatch_warnings_total |
+| haproxy_backend_status |
+| haproxy_backend_weight |
+| haproxy_backend_active_servers |
+| haproxy_backend_backup_servers |
+| haproxy_backend_check_up_down_total |
+| haproxy_backend_check_last_change_seconds |
+| haproxy_backend_downtime_seconds_total |
+| haproxy_backend_loadbalanced_total |
+| haproxy_backend_max_session_rate |
+| haproxy_backend_http_responses_total |
+| haproxy_backend_http_requests_total |
+| haproxy_backend_client_aborts_total |
+| haproxy_backend_server_aborts_total |
+| haproxy_backend_http_comp_bytes_in_total |
+| haproxy_backend_http_comp_bytes_out_total |
+| haproxy_backend_http_comp_bytes_bypassed_total |
+| haproxy_backend_http_comp_responses_total |
+| haproxy_backend_last_session_seconds |
+| haproxy_backend_queue_time_average_seconds |
+| haproxy_backend_connect_time_average_seconds |
+| haproxy_backend_response_time_average_seconds |
+| haproxy_backend_total_time_average_seconds |
+| haproxy_backend_failed_header_rewriting_total |
+| haproxy_backend_connection_attempts_total |
+| haproxy_backend_connection_reuses_total |
+| haproxy_backend_http_cache_lookups_total |
+| haproxy_backend_http_cache_hits_total |
+| haproxy_backend_max_queue_time_seconds |
+| haproxy_backend_max_connect_time_seconds |
+| haproxy_backend_max_response_time_seconds |
+| haproxy_backend_max_total_time_seconds |
+| haproxy_backend_internal_errors_total |
+| haproxy_backend_uweight |
+| haproxy_backend_agg_server_status |
+| haproxy_backend_agg_check_status |
++-----------------------------------------------------+
+
+* Server metrics
+
++----------------------------------------------------+
+| Metric name |
++----------------------------------------------------+
+| haproxy_server_current_queue |
+| haproxy_server_max_queue |
+| haproxy_server_current_sessions |
+| haproxy_server_max_sessions |
+| haproxy_server_limit_sessions |
+| haproxy_server_sessions_total |
+| haproxy_server_bytes_in_total |
+| haproxy_server_bytes_out_total |
+| haproxy_server_responses_denied_total |
+| haproxy_server_connection_errors_total |
+| haproxy_server_response_errors_total |
+| haproxy_server_retry_warnings_total |
+| haproxy_server_redispatch_warnings_total |
+| haproxy_server_status |
+| haproxy_server_weight |
+| haproxy_server_check_failures_total |
+| haproxy_server_check_up_down_total |
+| haproxy_server_check_last_change_seconds |
+| haproxy_server_downtime_seconds_total |
+| haproxy_server_queue_limit |
+| haproxy_server_current_throttle |
+| haproxy_server_loadbalanced_total |
+| haproxy_server_max_session_rate |
+| haproxy_server_check_status |
+| haproxy_server_check_code |
+| haproxy_server_check_duration_seconds |
+| haproxy_server_http_responses_total |
+| haproxy_server_client_aborts_total |
+| haproxy_server_server_aborts_total |
+| haproxy_server_last_session_seconds |
+| haproxy_server_queue_time_average_seconds |
+| haproxy_server_connect_time_average_seconds |
+| haproxy_server_response_time_average_seconds |
+| haproxy_server_total_time_average_seconds |
+| haproxy_server_failed_header_rewriting_total |
+| haproxy_server_connection_attempts_total |
+| haproxy_server_connection_reuses_total |
+| haproxy_server_idle_connections_current |
+| haproxy_server_idle_connections_limit |
+| haproxy_server_max_queue_time_seconds |
+| haproxy_server_max_connect_time_seconds |
+| haproxy_server_max_response_time_seconds |
+| haproxy_server_max_total_time_seconds |
+| haproxy_server_internal_errors_total |
+| haproxy_server_unsafe_idle_connections_current |
+| haproxy_server_safe_idle_connections_current |
+| haproxy_server_used_connections_current |
+| haproxy_server_need_connections_current |
+| haproxy_server_uweight |
++----------------------------------------------------+
+
+* Stick table metrics
+
++----------------------------------------------------+
+| Metric name |
++----------------------------------------------------+
+| haproxy_sticktable_size |
+| haproxy_sticktable_used |
++----------------------------------------------------+
diff --git a/addons/promex/service-prometheus.c b/addons/promex/service-prometheus.c
new file mode 100644
index 0000000..e3a6475
--- /dev/null
+++ b/addons/promex/service-prometheus.c
@@ -0,0 +1,1659 @@
+/*
+ * Promex is a Prometheus exporter for HAProxy
+ *
+ * It is highly inspired by the official Prometheus exporter.
+ * See: https://github.com/prometheus/haproxy_exporter
+ *
+ * Copyright 2019 Christopher Faulet <cfaulet@haproxy.com>
+ *
+ * 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.
+ *
+ */
+
+#include <haproxy/action-t.h>
+#include <haproxy/api.h>
+#include <haproxy/applet.h>
+#include <haproxy/backend.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/check.h>
+#include <haproxy/frontend.h>
+#include <haproxy/global.h>
+#include <haproxy/http.h>
+#include <haproxy/http_ana.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/htx.h>
+#include <haproxy/list.h>
+#include <haproxy/listener.h>
+#include <haproxy/log.h>
+#include <haproxy/proxy.h>
+#include <haproxy/sample.h>
+#include <haproxy/sc_strm.h>
+#include <haproxy/server.h>
+#include <haproxy/stats.h>
+#include <haproxy/stconn.h>
+#include <haproxy/stream.h>
+#include <haproxy/task.h>
+#include <haproxy/tools.h>
+#include <haproxy/version.h>
+
+/* Prometheus exporter applet states (appctx->st0) */
+enum {
+ PROMEX_ST_INIT = 0, /* initialized */
+ PROMEX_ST_HEAD, /* send headers before dump */
+ PROMEX_ST_DUMP, /* dumping stats */
+ PROMEX_ST_DONE, /* finished */
+ PROMEX_ST_END, /* treatment terminated */
+};
+
+/* Prometheus exporter dumper states (appctx->st1) */
+enum {
+ PROMEX_DUMPER_INIT = 0, /* initialized */
+ PROMEX_DUMPER_GLOBAL, /* dump metrics of globals */
+ PROMEX_DUMPER_FRONT, /* dump metrics of frontend proxies */
+ PROMEX_DUMPER_BACK, /* dump metrics of backend proxies */
+ PROMEX_DUMPER_LI, /* dump metrics of listeners */
+ PROMEX_DUMPER_SRV, /* dump metrics of servers */
+ PROMEX_DUMPER_STICKTABLE, /* dump metrics of stick tables */
+ PROMEX_DUMPER_DONE, /* finished */
+};
+
+/* Prometheus exporter flags (ctx->flags) */
+#define PROMEX_FL_METRIC_HDR 0x00000001
+#define PROMEX_FL_INFO_METRIC 0x00000002
+#define PROMEX_FL_FRONT_METRIC 0x00000004
+#define PROMEX_FL_BACK_METRIC 0x00000008
+#define PROMEX_FL_SRV_METRIC 0x00000010
+#define PROMEX_FL_LI_METRIC 0x00000020
+#define PROMEX_FL_STICKTABLE_METRIC 0x00000040
+#define PROMEX_FL_SCOPE_GLOBAL 0x00000080
+#define PROMEX_FL_SCOPE_FRONT 0x00000100
+#define PROMEX_FL_SCOPE_BACK 0x00000200
+#define PROMEX_FL_SCOPE_SERVER 0x00000400
+#define PROMEX_FL_SCOPE_LI 0x00000800
+#define PROMEX_FL_SCOPE_STICKTABLE 0x00001000
+#define PROMEX_FL_NO_MAINT_SRV 0x00002000
+
+#define PROMEX_FL_SCOPE_ALL (PROMEX_FL_SCOPE_GLOBAL | PROMEX_FL_SCOPE_FRONT | \
+ PROMEX_FL_SCOPE_LI | PROMEX_FL_SCOPE_BACK | \
+ PROMEX_FL_SCOPE_SERVER | PROMEX_FL_SCOPE_STICKTABLE)
+
+/* the context of the applet */
+struct promex_ctx {
+ struct proxy *px; /* current proxy */
+ struct stktable *st; /* current table */
+ struct listener *li; /* current listener */
+ struct server *sv; /* current server */
+ unsigned int flags; /* PROMEX_FL_* */
+ unsigned field_num; /* current field number (ST_F_* etc) */
+ int obj_state; /* current state among PROMEX_{FRONT|BACK|SRV|LI}_STATE_* */
+};
+
+/* Promtheus metric type (gauge or counter) */
+enum promex_mt_type {
+ PROMEX_MT_GAUGE = 1,
+ PROMEX_MT_COUNTER = 2,
+};
+
+/* The max length for metrics name. It is a hard limit but it should be
+ * enough.
+ */
+#define PROMEX_MAX_NAME_LEN 128
+
+/* The expected max length for a metric dump, including its header lines. It is
+ * just a soft limit to avoid extra work. We don't try to dump a metric if less
+ * than this size is available in the HTX.
+ */
+#define PROMEX_MAX_METRIC_LENGTH 512
+
+/* The max number of labels per metric */
+#define PROMEX_MAX_LABELS 8
+
+/* Describe a prometheus metric */
+struct promex_metric {
+ const struct ist n; /* The metric name */
+ enum promex_mt_type type; /* The metric type (gauge or counter) */
+ unsigned int flags; /* PROMEX_FL_* flags */
+};
+
+/* Describe a prometheus metric label. It is just a key/value pair */
+struct promex_label {
+ struct ist name;
+ struct ist value;
+};
+
+/* Global metrics */
+const struct promex_metric promex_global_metrics[INF_TOTAL_FIELDS] = {
+ //[INF_NAME] ignored
+ //[INF_VERSION], ignored
+ //[INF_RELEASE_DATE] ignored
+ [INF_NBTHREAD] = { .n = IST("nbthread"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_NBPROC] = { .n = IST("nbproc"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_PROCESS_NUM] = { .n = IST("relative_process_id"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ //[INF_PID] ignored
+ //[INF_UPTIME] ignored
+ [INF_UPTIME_SEC] = { .n = IST("uptime_seconds"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_START_TIME_SEC] = { .n = IST("start_time_seconds"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ //[INF_MEMMAX_MB] ignored
+ [INF_MEMMAX_BYTES] = { .n = IST("max_memory_bytes"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ //[INF_POOL_ALLOC_MB] ignored
+ [INF_POOL_ALLOC_BYTES] = { .n = IST("pool_allocated_bytes"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ //[INF_POOL_USED_MB] ignored
+ [INF_POOL_USED_BYTES] = { .n = IST("pool_used_bytes"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_POOL_FAILED] = { .n = IST("pool_failures_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_ULIMIT_N] = { .n = IST("max_fds"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAXSOCK] = { .n = IST("max_sockets"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAXCONN] = { .n = IST("max_connections"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_HARD_MAXCONN] = { .n = IST("hard_max_connections"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CURR_CONN] = { .n = IST("current_connections"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CUM_CONN] = { .n = IST("connections_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CUM_REQ] = { .n = IST("requests_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAX_SSL_CONNS] = { .n = IST("max_ssl_connections"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CURR_SSL_CONNS] = { .n = IST("current_ssl_connections"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CUM_SSL_CONNS] = { .n = IST("ssl_connections_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAXPIPES] = { .n = IST("max_pipes"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_PIPES_USED] = { .n = IST("pipes_used_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_PIPES_FREE] = { .n = IST("pipes_free_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CONN_RATE] = { .n = IST("current_connection_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CONN_RATE_LIMIT] = { .n = IST("limit_connection_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAX_CONN_RATE] = { .n = IST("max_connection_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SESS_RATE] = { .n = IST("current_session_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SESS_RATE_LIMIT] = { .n = IST("limit_session_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAX_SESS_RATE] = { .n = IST("max_session_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_RATE] = { .n = IST("current_ssl_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_RATE_LIMIT] = { .n = IST("limit_ssl_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAX_SSL_RATE] = { .n = IST("max_ssl_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_FRONTEND_KEY_RATE] = { .n = IST("current_frontend_ssl_key_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_FRONTEND_MAX_KEY_RATE] = { .n = IST("max_frontend_ssl_key_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_FRONTEND_SESSION_REUSE_PCT] = { .n = IST("frontend_ssl_reuse"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_BACKEND_KEY_RATE] = { .n = IST("current_backend_ssl_key_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_BACKEND_MAX_KEY_RATE] = { .n = IST("max_backend_ssl_key_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_CACHE_LOOKUPS] = { .n = IST("ssl_cache_lookups_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_SSL_CACHE_MISSES] = { .n = IST("ssl_cache_misses_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_COMPRESS_BPS_IN] = { .n = IST("http_comp_bytes_in_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_COMPRESS_BPS_OUT] = { .n = IST("http_comp_bytes_out_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_COMPRESS_BPS_RATE_LIM] = { .n = IST("limit_http_comp"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_ZLIB_MEM_USAGE] = { .n = IST("current_zlib_memory"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_MAX_ZLIB_MEM_USAGE] = { .n = IST("max_zlib_memory"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_TASKS] = { .n = IST("current_tasks"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_RUN_QUEUE] = { .n = IST("current_run_queue"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_IDLE_PCT] = { .n = IST("idle_time_percent"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ //[INF_NODE] ignored
+ //[INF_DESCRIPTION] ignored
+ [INF_STOPPING] = { .n = IST("stopping"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_JOBS] = { .n = IST("jobs"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_UNSTOPPABLE_JOBS] = { .n = IST("unstoppable_jobs"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_LISTENERS] = { .n = IST("listeners"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_ACTIVE_PEERS] = { .n = IST("active_peers"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_CONNECTED_PEERS] = { .n = IST("connected_peers"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_DROPPED_LOGS] = { .n = IST("dropped_logs_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_BUSY_POLLING] = { .n = IST("busy_polling_enabled"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_FAILED_RESOLUTIONS] = { .n = IST("failed_resolutions"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_TOTAL_BYTES_OUT] = { .n = IST("bytes_out_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_TOTAL_SPLICED_BYTES_OUT] = { .n = IST("spliced_bytes_out_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_BYTES_OUT_RATE] = { .n = IST("bytes_out_rate"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+ //[INF_DEBUG_COMMANDS_ISSUED] ignored
+ [INF_CUM_LOG_MSGS] = { .n = IST("recv_logs_total"), .type = PROMEX_MT_COUNTER, .flags = PROMEX_FL_INFO_METRIC },
+ [INF_BUILD_INFO] = { .n = IST("build_info"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_INFO_METRIC },
+};
+
+/* frontend/backend/server fields */
+const struct promex_metric promex_st_metrics[ST_F_TOTAL_FIELDS] = {
+ //[ST_F_PXNAME] ignored
+ //[ST_F_SVNAME] ignored
+ [ST_F_QCUR] = { .n = IST("current_queue"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_QMAX] = { .n = IST("max_queue"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_SCUR] = { .n = IST("current_sessions"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_SMAX] = { .n = IST("max_sessions"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_SLIM] = { .n = IST("limit_sessions"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_STOT] = { .n = IST("sessions_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_BIN] = { .n = IST("bytes_in_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_BOUT] = { .n = IST("bytes_out_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_DREQ] = { .n = IST("requests_denied_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_DRESP] = { .n = IST("responses_denied_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_EREQ] = { .n = IST("request_errors_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC ) },
+ [ST_F_ECON] = { .n = IST("connection_errors_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_ERESP] = { .n = IST("response_errors_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_WRETR] = { .n = IST("retry_warnings_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_WREDIS] = { .n = IST("redispatch_warnings_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_STATUS] = { .n = IST("status"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_WEIGHT] = { .n = IST("weight"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_ACT] = { .n = IST("active_servers"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC ) },
+ [ST_F_BCK] = { .n = IST("backup_servers"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC ) },
+ [ST_F_CHKFAIL] = { .n = IST("check_failures_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_CHKDOWN] = { .n = IST("check_up_down_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_LASTCHG] = { .n = IST("check_last_change_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_DOWNTIME] = { .n = IST("downtime_seconds_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_QLIMIT] = { .n = IST("queue_limit"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ //[ST_F_PID] ignored
+ //[ST_F_IID] ignored
+ //[ST_F_SID] ignored
+ [ST_F_THROTTLE] = { .n = IST("current_throttle"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_LBTOT] = { .n = IST("loadbalanced_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ //[ST_F_TRACKED] ignored
+ //[ST_F_TYPE] ignored
+ //[ST_F_RATE] ignored
+ [ST_F_RATE_LIM] = { .n = IST("limit_session_rate"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC ) },
+ [ST_F_RATE_MAX] = { .n = IST("max_session_rate"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_CHECK_STATUS] = { .n = IST("check_status"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_CHECK_CODE] = { .n = IST("check_code"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_CHECK_DURATION] = { .n = IST("check_duration_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_HRSP_1XX] = { .n = IST("http_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_HRSP_2XX] = { .n = IST("http_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_HRSP_3XX] = { .n = IST("http_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_HRSP_4XX] = { .n = IST("http_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_HRSP_5XX] = { .n = IST("http_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_HRSP_OTHER] = { .n = IST("http_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ //[ST_F_HANAFAIL] ignored
+ //[ST_F_REQ_RATE] ignored
+ [ST_F_REQ_RATE_MAX] = { .n = IST("http_requests_rate_max"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC ) },
+ [ST_F_REQ_TOT] = { .n = IST("http_requests_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_CLI_ABRT] = { .n = IST("client_aborts_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_SRV_ABRT] = { .n = IST("server_aborts_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_COMP_IN] = { .n = IST("http_comp_bytes_in_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_COMP_OUT] = { .n = IST("http_comp_bytes_out_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_COMP_BYP] = { .n = IST("http_comp_bytes_bypassed_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_COMP_RSP] = { .n = IST("http_comp_responses_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_LASTSESS] = { .n = IST("last_session_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ //[ST_F_LAST_CHK] ignored
+ //[ST_F_LAST_AGT] ignored
+ [ST_F_QTIME] = { .n = IST("queue_time_average_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_CTIME] = { .n = IST("connect_time_average_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_RTIME] = { .n = IST("response_time_average_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_TTIME] = { .n = IST("total_time_average_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ //[ST_F_AGENT_STATUS] ignored
+ //[ST_F_AGENT_CODE] ignored
+ //[ST_F_AGENT_DURATION] ignored
+ //[ST_F_CHECK_DESC] ignored
+ //[ST_F_AGENT_DESC] ignored
+ //[ST_F_CHECK_RISE] ignored
+ //[ST_F_CHECK_FALL] ignored
+ //[ST_F_CHECK_HEALTH] ignored
+ //[ST_F_AGENT_RISE] ignored
+ //[ST_F_AGENT_FALL] ignored
+ //[ST_F_AGENT_HEALTH] ignored
+ //[ST_F_ADDR] ignored
+ //[ST_F_COOKIE] ignored
+ //[ST_F_MODE] ignored
+ //[ST_F_ALGO] ignored
+ //[ST_F_CONN_RATE] ignored
+ [ST_F_CONN_RATE_MAX] = { .n = IST("connections_rate_max"), .type = PROMEX_MT_GAUGE, .flags = (PROMEX_FL_FRONT_METRIC ) },
+ [ST_F_CONN_TOT] = { .n = IST("connections_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC ) },
+ [ST_F_INTERCEPTED] = { .n = IST("intercepted_requests_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC ) },
+ [ST_F_DCON] = { .n = IST("denied_connections_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC ) },
+ [ST_F_DSES] = { .n = IST("denied_sessions_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC ) },
+ [ST_F_WREW] = { .n = IST("failed_header_rewriting_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_CONNECT] = { .n = IST("connection_attempts_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_REUSE] = { .n = IST("connection_reuses_total"), .type = PROMEX_MT_COUNTER, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_CACHE_LOOKUPS] = { .n = IST("http_cache_lookups_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_CACHE_HITS] = { .n = IST("http_cache_hits_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_BACK_METRIC ) },
+ [ST_F_SRV_ICUR] = { .n = IST("idle_connections_current"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_SRV_ILIM] = { .n = IST("idle_connections_limit"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_QT_MAX] = { .n = IST("max_queue_time_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_CT_MAX] = { .n = IST("max_connect_time_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_RT_MAX] = { .n = IST("max_response_time_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_TT_MAX] = { .n = IST("max_total_time_seconds"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_EINT] = { .n = IST("internal_errors_total"), .type = PROMEX_MT_COUNTER, .flags = (PROMEX_FL_FRONT_METRIC | PROMEX_FL_LI_METRIC | PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_IDLE_CONN_CUR] = { .n = IST("unsafe_idle_connections_current"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_SAFE_CONN_CUR] = { .n = IST("safe_idle_connections_current"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_USED_CONN_CUR] = { .n = IST("used_connections_current"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_NEED_CONN_EST] = { .n = IST("need_connections_current"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_SRV_METRIC) },
+ [ST_F_UWEIGHT] = { .n = IST("uweight"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC | PROMEX_FL_SRV_METRIC) },
+ [ST_F_AGG_SRV_CHECK_STATUS] = { .n = IST("agg_server_check_status"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC ) },
+ [ST_F_AGG_SRV_STATUS ] = { .n = IST("agg_server_status"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC ) },
+ [ST_F_AGG_CHECK_STATUS] = { .n = IST("agg_check_status"), .type = PROMEX_MT_GAUGE, .flags = ( PROMEX_FL_BACK_METRIC ) },
+};
+
+/* Description of overridden stats fields */
+const struct ist promex_st_metric_desc[ST_F_TOTAL_FIELDS] = {
+ [ST_F_STATUS] = IST("Current status of the service, per state label value."),
+ [ST_F_CHECK_STATUS] = IST("Status of last health check, per state label value."),
+ [ST_F_CHECK_CODE] = IST("layer5-7 code, if available of the last health check."),
+ [ST_F_CHECK_DURATION] = IST("Total duration of the latest server health check, in seconds."),
+ [ST_F_QTIME] = IST("Avg. queue time for last 1024 successful connections."),
+ [ST_F_CTIME] = IST("Avg. connect time for last 1024 successful connections."),
+ [ST_F_RTIME] = IST("Avg. response time for last 1024 successful connections."),
+ [ST_F_TTIME] = IST("Avg. total time for last 1024 successful connections."),
+ [ST_F_QT_MAX] = IST("Maximum observed time spent in the queue"),
+ [ST_F_CT_MAX] = IST("Maximum observed time spent waiting for a connection to complete"),
+ [ST_F_RT_MAX] = IST("Maximum observed time spent waiting for a server response"),
+ [ST_F_TT_MAX] = IST("Maximum observed total request+response time (request+queue+connect+response+processing)"),
+};
+
+/* stick table base fields */
+enum sticktable_field {
+ STICKTABLE_SIZE = 0,
+ STICKTABLE_USED,
+ /* must always be the last one */
+ STICKTABLE_TOTAL_FIELDS
+};
+
+const struct promex_metric promex_sticktable_metrics[STICKTABLE_TOTAL_FIELDS] = {
+ [STICKTABLE_SIZE] = { .n = IST("size"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_STICKTABLE_METRIC },
+ [STICKTABLE_USED] = { .n = IST("used"), .type = PROMEX_MT_GAUGE, .flags = PROMEX_FL_STICKTABLE_METRIC },
+};
+
+/* stick table base description */
+const struct ist promex_sticktable_metric_desc[STICKTABLE_TOTAL_FIELDS] = {
+ [STICKTABLE_SIZE] = IST("Stick table size."),
+ [STICKTABLE_USED] = IST("Number of entries used in this stick table."),
+};
+
+/* Specific labels for all ST_F_HRSP_* fields */
+const struct ist promex_hrsp_code[1 + ST_F_HRSP_OTHER - ST_F_HRSP_1XX] = {
+ [ST_F_HRSP_1XX - ST_F_HRSP_1XX] = IST("1xx"),
+ [ST_F_HRSP_2XX - ST_F_HRSP_1XX] = IST("2xx"),
+ [ST_F_HRSP_3XX - ST_F_HRSP_1XX] = IST("3xx"),
+ [ST_F_HRSP_4XX - ST_F_HRSP_1XX] = IST("4xx"),
+ [ST_F_HRSP_5XX - ST_F_HRSP_1XX] = IST("5xx"),
+ [ST_F_HRSP_OTHER - ST_F_HRSP_1XX] = IST("other"),
+};
+
+enum promex_front_state {
+ PROMEX_FRONT_STATE_DOWN = 0,
+ PROMEX_FRONT_STATE_UP,
+
+ PROMEX_FRONT_STATE_COUNT /* must be last */
+};
+
+const struct ist promex_front_st[PROMEX_FRONT_STATE_COUNT] = {
+ [PROMEX_FRONT_STATE_DOWN] = IST("DOWN"),
+ [PROMEX_FRONT_STATE_UP] = IST("UP"),
+};
+
+enum promex_back_state {
+ PROMEX_BACK_STATE_DOWN = 0,
+ PROMEX_BACK_STATE_UP,
+
+ PROMEX_BACK_STATE_COUNT /* must be last */
+};
+
+const struct ist promex_back_st[PROMEX_BACK_STATE_COUNT] = {
+ [PROMEX_BACK_STATE_DOWN] = IST("DOWN"),
+ [PROMEX_BACK_STATE_UP] = IST("UP"),
+};
+
+enum promex_srv_state {
+ PROMEX_SRV_STATE_DOWN = 0,
+ PROMEX_SRV_STATE_UP,
+ PROMEX_SRV_STATE_MAINT,
+ PROMEX_SRV_STATE_DRAIN,
+ PROMEX_SRV_STATE_NOLB,
+
+ PROMEX_SRV_STATE_COUNT /* must be last */
+};
+
+const struct ist promex_srv_st[PROMEX_SRV_STATE_COUNT] = {
+ [PROMEX_SRV_STATE_DOWN] = IST("DOWN"),
+ [PROMEX_SRV_STATE_UP] = IST("UP"),
+ [PROMEX_SRV_STATE_MAINT] = IST("MAINT"),
+ [PROMEX_SRV_STATE_DRAIN] = IST("DRAIN"),
+ [PROMEX_SRV_STATE_NOLB] = IST("NOLB"),
+};
+
+/* Return the server status. */
+enum promex_srv_state promex_srv_status(struct server *sv)
+{
+ int state = PROMEX_SRV_STATE_DOWN;
+
+ if (sv->cur_state == SRV_ST_RUNNING || sv->cur_state == SRV_ST_STARTING) {
+ state = PROMEX_SRV_STATE_UP;
+ if (sv->cur_admin & SRV_ADMF_DRAIN)
+ state = PROMEX_SRV_STATE_DRAIN;
+ }
+ else if (sv->cur_state == SRV_ST_STOPPING)
+ state = PROMEX_SRV_STATE_NOLB;
+
+ if (sv->cur_admin & SRV_ADMF_MAINT)
+ state = PROMEX_SRV_STATE_MAINT;
+
+ return state;
+}
+
+/* Convert a field to its string representation and write it in <out>, followed
+ * by a newline, if there is enough space. non-numeric value are converted in
+ * "NaN" because Prometheus only support numerical values (but it is unexepceted
+ * to process this kind of value). It returns 1 on success. Otherwise, it
+ * returns 0. The buffer's length must not exceed <max> value.
+ */
+static int promex_metric_to_str(struct buffer *out, struct field *f, size_t max)
+{
+ int ret = 0;
+
+ switch (field_format(f, 0)) {
+ case FF_EMPTY: ret = chunk_strcat(out, "NaN\n"); break;
+ case FF_S32: ret = chunk_appendf(out, "%d\n", f->u.s32); break;
+ case FF_U32: ret = chunk_appendf(out, "%u\n", f->u.u32); break;
+ case FF_S64: ret = chunk_appendf(out, "%lld\n", (long long)f->u.s64); break;
+ case FF_U64: ret = chunk_appendf(out, "%llu\n", (unsigned long long)f->u.u64); break;
+ case FF_FLT: ret = chunk_appendf(out, "%f\n", f->u.flt); break;
+ case FF_STR: ret = chunk_strcat(out, "NaN\n"); break;
+ default: ret = chunk_strcat(out, "NaN\n"); break;
+ }
+ if (!ret || out->data > max)
+ return 0;
+ return 1;
+}
+
+/* Dump the header lines for <metric>. It is its #HELP and #TYPE strings. It
+ * returns 1 on success. Otherwise, if <out> length exceeds <max>, it returns 0.
+ */
+static int promex_dump_metric_header(struct appctx *appctx, struct htx *htx,
+ const struct promex_metric *metric, const struct ist name,
+ struct ist *out, size_t max)
+{
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct ist type;
+ struct ist desc;
+
+ switch (metric->type) {
+ case PROMEX_MT_COUNTER:
+ type = ist("counter");
+ break;
+ default:
+ type = ist("gauge");
+ }
+
+ if (istcat(out, ist("# HELP "), max) == -1 ||
+ istcat(out, name, max) == -1 ||
+ istcat(out, ist(" "), max) == -1)
+ goto full;
+
+ if (metric->flags & PROMEX_FL_INFO_METRIC)
+ desc = ist(info_fields[ctx->field_num].desc);
+ else if (metric->flags & PROMEX_FL_STICKTABLE_METRIC)
+ desc = promex_sticktable_metric_desc[ctx->field_num];
+ else if (!isttest(promex_st_metric_desc[ctx->field_num]))
+ desc = ist(stat_fields[ctx->field_num].desc);
+ else
+ desc = promex_st_metric_desc[ctx->field_num];
+
+ if (istcat(out, desc, max) == -1 ||
+ istcat(out, ist("\n# TYPE "), max) == -1 ||
+ istcat(out, name, max) == -1 ||
+ istcat(out, ist(" "), max) == -1 ||
+ istcat(out, type, max) == -1 ||
+ istcat(out, ist("\n"), max) == -1)
+ goto full;
+
+ return 1;
+
+ full:
+ return 0;
+}
+
+/* Dump the line for <metric>. It starts by the metric name followed by its
+ * labels (proxy name, server name...) between braces and finally its value. If
+ * not already done, the header lines are dumped first. It returns 1 on
+ * success. Otherwise if <out> length exceeds <max>, it returns 0.
+ */
+static int promex_dump_metric(struct appctx *appctx, struct htx *htx, struct ist prefix,
+ const struct promex_metric *metric, struct field *val,
+ struct promex_label *labels, struct ist *out, size_t max)
+{
+ struct ist name = { .ptr = (char[PROMEX_MAX_NAME_LEN]){ 0 }, .len = 0 };
+ struct promex_ctx *ctx = appctx->svcctx;
+ size_t len = out->len;
+
+ if (out->len + PROMEX_MAX_METRIC_LENGTH > max)
+ return 0;
+
+ /* Fill the metric name */
+ istcat(&name, prefix, PROMEX_MAX_NAME_LEN);
+ istcat(&name, metric->n, PROMEX_MAX_NAME_LEN);
+
+
+ if ((ctx->flags & PROMEX_FL_METRIC_HDR) &&
+ !promex_dump_metric_header(appctx, htx, metric, name, out, max))
+ goto full;
+
+ if (istcat(out, name, max) == -1)
+ goto full;
+
+ if (isttest(labels[0].name)) {
+ int i;
+
+ if (istcat(out, ist("{"), max) == -1)
+ goto full;
+
+ for (i = 0; isttest(labels[i].name); i++) {
+ if (!isttest(labels[i].value))
+ continue;
+
+ if ((i && istcat(out, ist(","), max) == -1) ||
+ istcat(out, labels[i].name, max) == -1 ||
+ istcat(out, ist("=\""), max) == -1 ||
+ istcat(out, labels[i].value, max) == -1 ||
+ istcat(out, ist("\""), max) == -1)
+ goto full;
+ }
+
+ if (istcat(out, ist("}"), max) == -1)
+ goto full;
+
+ }
+
+ if (istcat(out, ist(" "), max) == -1)
+ goto full;
+
+ trash.data = out->len;
+ if (!promex_metric_to_str(&trash, val, max))
+ goto full;
+ out->len = trash.data;
+
+ ctx->flags &= ~PROMEX_FL_METRIC_HDR;
+ return 1;
+ full:
+ // Restore previous length
+ out->len = len;
+ return 0;
+
+}
+
+
+/* Dump global metrics (prefixed by "haproxy_process_"). It returns 1 on success,
+ * 0 if <htx> is full and -1 in case of any error. */
+static int promex_dump_global_metrics(struct appctx *appctx, struct htx *htx)
+{
+ static struct ist prefix = IST("haproxy_process_");
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct field val;
+ struct channel *chn = sc_ic(appctx_sc(appctx));
+ struct ist out = ist2(trash.area, 0);
+ size_t max = htx_get_max_blksz(htx, channel_htx_recv_max(chn, htx));
+ int ret = 1;
+
+ if (!stats_fill_info(info, INF_TOTAL_FIELDS, 0))
+ return -1;
+
+ for (; ctx->field_num < INF_TOTAL_FIELDS; ctx->field_num++) {
+ struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
+
+ if (!(promex_global_metrics[ctx->field_num].flags & ctx->flags))
+ continue;
+
+ switch (ctx->field_num) {
+ case INF_BUILD_INFO:
+ labels[0].name = ist("version");
+ labels[0].value = ist(HAPROXY_VERSION);
+ val = mkf_u32(FN_GAUGE, 1);
+ break;
+
+ default:
+ val = info[ctx->field_num];
+ }
+
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_global_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+
+ ctx->flags |= PROMEX_FL_METRIC_HDR;
+ }
+
+ end:
+ if (out.len) {
+ if (!htx_add_data_atonce(htx, out))
+ return -1; /* Unexpected and unrecoverable error */
+ channel_add_input(chn, out.len);
+ }
+ return ret;
+ full:
+ ret = 0;
+ goto end;
+}
+
+/* Dump frontends metrics (prefixed by "haproxy_frontend_"). It returns 1 on success,
+ * 0 if <htx> is full and -1 in case of any error. */
+static int promex_dump_front_metrics(struct appctx *appctx, struct htx *htx)
+{
+ static struct ist prefix = IST("haproxy_frontend_");
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct proxy *px;
+ struct field val;
+ struct channel *chn = sc_ic(appctx_sc(appctx));
+ struct ist out = ist2(trash.area, 0);
+ size_t max = htx_get_max_blksz(htx, channel_htx_recv_max(chn, htx));
+ struct field *stats = stat_l[STATS_DOMAIN_PROXY];
+ int ret = 1;
+ enum promex_front_state state;
+
+ for (;ctx->field_num < ST_F_TOTAL_FIELDS; ctx->field_num++) {
+ if (!(promex_st_metrics[ctx->field_num].flags & ctx->flags))
+ continue;
+
+ while (ctx->px) {
+ struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
+
+ px = ctx->px;
+
+ labels[0].name = ist("proxy");
+ labels[0].value = ist2(px->id, strlen(px->id));
+
+ /* skip the disabled proxies, global frontend and non-networked ones */
+ if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_FE))
+ goto next_px;
+
+ if (!stats_fill_fe_stats(px, stats, ST_F_TOTAL_FIELDS, &(ctx->field_num)))
+ return -1;
+
+ switch (ctx->field_num) {
+ case ST_F_STATUS:
+ state = !(px->flags & PR_FL_STOPPED);
+ for (; ctx->obj_state < PROMEX_FRONT_STATE_COUNT; ctx->obj_state++) {
+ labels[1].name = ist("state");
+ labels[1].value = promex_front_st[ctx->obj_state];
+ val = mkf_u32(FO_STATUS, state == ctx->obj_state);
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ goto next_px;
+ case ST_F_REQ_RATE_MAX:
+ case ST_F_REQ_TOT:
+ case ST_F_INTERCEPTED:
+ case ST_F_CACHE_LOOKUPS:
+ case ST_F_CACHE_HITS:
+ case ST_F_COMP_IN:
+ case ST_F_COMP_OUT:
+ case ST_F_COMP_BYP:
+ case ST_F_COMP_RSP:
+ if (px->mode != PR_MODE_HTTP)
+ goto next_px;
+ val = stats[ctx->field_num];
+ break;
+ case ST_F_HRSP_1XX:
+ case ST_F_HRSP_2XX:
+ case ST_F_HRSP_3XX:
+ case ST_F_HRSP_4XX:
+ case ST_F_HRSP_5XX:
+ case ST_F_HRSP_OTHER:
+ if (px->mode != PR_MODE_HTTP)
+ goto next_px;
+ if (ctx->field_num != ST_F_HRSP_1XX)
+ ctx->flags &= ~PROMEX_FL_METRIC_HDR;
+ labels[1].name = ist("code");
+ labels[1].value = promex_hrsp_code[ctx->field_num - ST_F_HRSP_1XX];
+ val = stats[ctx->field_num];
+ break;
+
+ default:
+ val = stats[ctx->field_num];
+ }
+
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ next_px:
+ ctx->px = px->next;
+ }
+ ctx->flags |= PROMEX_FL_METRIC_HDR;
+ ctx->px = proxies_list;
+ }
+
+ end:
+ if (out.len) {
+ if (!htx_add_data_atonce(htx, out))
+ return -1; /* Unexpected and unrecoverable error */
+ channel_add_input(chn, out.len);
+ }
+ return ret;
+ full:
+ ret = 0;
+ goto end;
+}
+
+/* Dump listener metrics (prefixed by "haproxy_listen_"). It returns 1 on
+ * success, 0 if <htx> is full and -1 in case of any error. */
+static int promex_dump_listener_metrics(struct appctx *appctx, struct htx *htx)
+{
+ static struct ist prefix = IST("haproxy_listener_");
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct proxy *px;
+ struct field val;
+ struct channel *chn = sc_ic(appctx_sc(appctx));
+ struct ist out = ist2(trash.area, 0);
+ size_t max = htx_get_max_blksz(htx, channel_htx_recv_max(chn, htx));
+ struct field *stats = stat_l[STATS_DOMAIN_PROXY];
+ struct listener *li;
+ int ret = 1;
+ enum li_status status;
+
+ for (;ctx->field_num < ST_F_TOTAL_FIELDS; ctx->field_num++) {
+ if (!(promex_st_metrics[ctx->field_num].flags & ctx->flags))
+ continue;
+
+ while (ctx->px) {
+ struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
+
+ px = ctx->px;
+
+ labels[0].name = ist("proxy");
+ labels[0].value = ist2(px->id, strlen(px->id));
+
+ /* skip the disabled proxies, global frontend and non-networked ones */
+ if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_FE))
+ goto next_px;
+
+ li = ctx->li;
+ list_for_each_entry_from(li, &px->conf.listeners, by_fe) {
+
+ if (!li->counters)
+ continue;
+
+ labels[1].name = ist("listener");
+ labels[1].value = ist2(li->name, strlen(li->name));
+
+ if (!stats_fill_li_stats(px, li, 0, stats,
+ ST_F_TOTAL_FIELDS, &(ctx->field_num)))
+ return -1;
+
+ switch (ctx->field_num) {
+ case ST_F_STATUS:
+ status = get_li_status(li);
+ for (; ctx->obj_state < LI_STATE_COUNT; ctx->obj_state++) {
+ val = mkf_u32(FO_STATUS, status == ctx->obj_state);
+ labels[2].name = ist("state");
+ labels[2].value = ist(li_status_st[ctx->obj_state]);
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ continue;
+ default:
+ val = stats[ctx->field_num];
+ }
+
+ if (!promex_dump_metric(appctx, htx, prefix,
+ &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+
+ next_px:
+ px = px->next;
+ ctx->px = px;
+ ctx->li = (px ? LIST_NEXT(&px->conf.listeners, struct listener *, by_fe) : NULL);
+ }
+ ctx->flags |= PROMEX_FL_METRIC_HDR;
+ ctx->px = proxies_list;
+ ctx->li = LIST_NEXT(&proxies_list->conf.listeners, struct listener *, by_fe);
+ }
+
+ end:
+ if (out.len) {
+ if (!htx_add_data_atonce(htx, out))
+ return -1; /* Unexpected and unrecoverable error */
+ channel_add_input(chn, out.len);
+ }
+ return ret;
+ full:
+ ctx->li = li;
+ ret = 0;
+ goto end;
+}
+
+/* Dump backends metrics (prefixed by "haproxy_backend_"). It returns 1 on success,
+ * 0 if <htx> is full and -1 in case of any error. */
+static int promex_dump_back_metrics(struct appctx *appctx, struct htx *htx)
+{
+ static struct ist prefix = IST("haproxy_backend_");
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct proxy *px;
+ struct server *sv;
+ struct field val;
+ struct channel *chn = sc_ic(appctx_sc(appctx));
+ struct ist out = ist2(trash.area, 0);
+ size_t max = htx_get_max_blksz(htx, channel_htx_recv_max(chn, htx));
+ struct field *stats = stat_l[STATS_DOMAIN_PROXY];
+ int ret = 1;
+ double secs;
+ enum promex_back_state bkd_state;
+ enum promex_srv_state srv_state;
+ enum healthcheck_status srv_check_status;
+
+ for (;ctx->field_num < ST_F_TOTAL_FIELDS; ctx->field_num++) {
+ if (!(promex_st_metrics[ctx->field_num].flags & ctx->flags))
+ continue;
+
+ while (ctx->px) {
+ struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
+ unsigned int srv_state_count[PROMEX_SRV_STATE_COUNT] = { 0 };
+ unsigned int srv_check_count[HCHK_STATUS_SIZE] = { 0 };
+ const char *check_state;
+
+ px = ctx->px;
+
+ labels[0].name = ist("proxy");
+ labels[0].value = ist2(px->id, strlen(px->id));
+
+ /* skip the disabled proxies, global frontend and non-networked ones */
+ if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE))
+ goto next_px;
+
+ if (!stats_fill_be_stats(px, 0, stats, ST_F_TOTAL_FIELDS, &(ctx->field_num)))
+ return -1;
+
+ switch (ctx->field_num) {
+ case ST_F_AGG_SRV_CHECK_STATUS: // DEPRECATED
+ case ST_F_AGG_SRV_STATUS:
+ if (!px->srv)
+ goto next_px;
+ sv = px->srv;
+ while (sv) {
+ srv_state = promex_srv_status(sv);
+ srv_state_count[srv_state] += 1;
+ sv = sv->next;
+ }
+ for (; ctx->obj_state < PROMEX_SRV_STATE_COUNT; ctx->obj_state++) {
+ val = mkf_u32(FN_GAUGE, srv_state_count[ctx->obj_state]);
+ labels[1].name = ist("state");
+ labels[1].value = promex_srv_st[ctx->obj_state];
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ goto next_px;
+ case ST_F_AGG_CHECK_STATUS:
+ if (!px->srv)
+ goto next_px;
+ sv = px->srv;
+ while (sv) {
+ srv_check_status = sv->check.status;
+ srv_check_count[srv_check_status] += 1;
+ sv = sv->next;
+ }
+ for (; ctx->obj_state < HCHK_STATUS_SIZE; ctx->obj_state++) {
+ if (get_check_status_result(ctx->obj_state) < CHK_RES_FAILED)
+ continue;
+ val = mkf_u32(FO_STATUS, srv_check_count[ctx->obj_state]);
+ check_state = get_check_status_info(ctx->obj_state);
+ labels[1].name = ist("state");
+ labels[1].value = ist(check_state);
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ goto next_px;
+ case ST_F_STATUS:
+ bkd_state = ((px->lbprm.tot_weight > 0 || !px->srv) ? 1 : 0);
+ for (; ctx->obj_state < PROMEX_BACK_STATE_COUNT; ctx->obj_state++) {
+ labels[1].name = ist("state");
+ labels[1].value = promex_back_st[ctx->obj_state];
+ val = mkf_u32(FO_STATUS, bkd_state == ctx->obj_state);
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ goto next_px;
+ case ST_F_QTIME:
+ secs = (double)swrate_avg(px->be_counters.q_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_CTIME:
+ secs = (double)swrate_avg(px->be_counters.c_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_RTIME:
+ secs = (double)swrate_avg(px->be_counters.d_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_TTIME:
+ secs = (double)swrate_avg(px->be_counters.t_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_QT_MAX:
+ secs = (double)px->be_counters.qtime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_CT_MAX:
+ secs = (double)px->be_counters.ctime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_RT_MAX:
+ secs = (double)px->be_counters.dtime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_TT_MAX:
+ secs = (double)px->be_counters.ttime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_REQ_TOT:
+ case ST_F_CACHE_LOOKUPS:
+ case ST_F_CACHE_HITS:
+ case ST_F_COMP_IN:
+ case ST_F_COMP_OUT:
+ case ST_F_COMP_BYP:
+ case ST_F_COMP_RSP:
+ if (px->mode != PR_MODE_HTTP)
+ goto next_px;
+ val = stats[ctx->field_num];
+ break;
+ case ST_F_HRSP_1XX:
+ case ST_F_HRSP_2XX:
+ case ST_F_HRSP_3XX:
+ case ST_F_HRSP_4XX:
+ case ST_F_HRSP_5XX:
+ case ST_F_HRSP_OTHER:
+ if (px->mode != PR_MODE_HTTP)
+ goto next_px;
+ if (ctx->field_num != ST_F_HRSP_1XX)
+ ctx->flags &= ~PROMEX_FL_METRIC_HDR;
+ labels[1].name = ist("code");
+ labels[1].value = promex_hrsp_code[ctx->field_num - ST_F_HRSP_1XX];
+ val = stats[ctx->field_num];
+ break;
+
+ default:
+ val = stats[ctx->field_num];
+ }
+
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ next_px:
+ ctx->px = px->next;
+ }
+ ctx->flags |= PROMEX_FL_METRIC_HDR;
+ ctx->px = proxies_list;
+ }
+
+ end:
+ if (out.len) {
+ if (!htx_add_data_atonce(htx, out))
+ return -1; /* Unexpected and unrecoverable error */
+ channel_add_input(chn, out.len);
+ }
+ return ret;
+ full:
+ ret = 0;
+ goto end;
+}
+
+/* Dump servers metrics (prefixed by "haproxy_server_"). It returns 1 on success,
+ * 0 if <htx> is full and -1 in case of any error. */
+static int promex_dump_srv_metrics(struct appctx *appctx, struct htx *htx)
+{
+ static struct ist prefix = IST("haproxy_server_");
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct proxy *px;
+ struct server *sv;
+ struct field val;
+ struct channel *chn = sc_ic(appctx_sc(appctx));
+ struct ist out = ist2(trash.area, 0);
+ size_t max = htx_get_max_blksz(htx, channel_htx_recv_max(chn, htx));
+ struct field *stats = stat_l[STATS_DOMAIN_PROXY];
+ int ret = 1;
+ double secs;
+ enum promex_srv_state state;
+ const char *check_state;
+
+ for (;ctx->field_num < ST_F_TOTAL_FIELDS; ctx->field_num++) {
+ if (!(promex_st_metrics[ctx->field_num].flags & ctx->flags))
+ continue;
+
+ while (ctx->px) {
+ struct promex_label labels[PROMEX_MAX_LABELS-1] = {};
+
+ px = ctx->px;
+
+ labels[0].name = ist("proxy");
+ labels[0].value = ist2(px->id, strlen(px->id));
+
+ /* skip the disabled proxies, global frontend and non-networked ones */
+ if ((px->flags & PR_FL_DISABLED) || px->uuid <= 0 || !(px->cap & PR_CAP_BE))
+ goto next_px;
+
+ while (ctx->sv) {
+ sv = ctx->sv;
+
+ labels[1].name = ist("server");
+ labels[1].value = ist2(sv->id, strlen(sv->id));
+
+ if (!stats_fill_sv_stats(px, sv, 0, stats, ST_F_TOTAL_FIELDS, &(ctx->field_num)))
+ return -1;
+
+ if ((ctx->flags & PROMEX_FL_NO_MAINT_SRV) && (sv->cur_admin & SRV_ADMF_MAINT))
+ goto next_sv;
+
+ switch (ctx->field_num) {
+ case ST_F_STATUS:
+ state = promex_srv_status(sv);
+ for (; ctx->obj_state < PROMEX_SRV_STATE_COUNT; ctx->obj_state++) {
+ val = mkf_u32(FO_STATUS, state == ctx->obj_state);
+ labels[2].name = ist("state");
+ labels[2].value = promex_srv_st[ctx->obj_state];
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ goto next_sv;
+ case ST_F_QTIME:
+ secs = (double)swrate_avg(sv->counters.q_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_CTIME:
+ secs = (double)swrate_avg(sv->counters.c_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_RTIME:
+ secs = (double)swrate_avg(sv->counters.d_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_TTIME:
+ secs = (double)swrate_avg(sv->counters.t_time, TIME_STATS_SAMPLES) / 1000.0;
+ val = mkf_flt(FN_AVG, secs);
+ break;
+ case ST_F_QT_MAX:
+ secs = (double)sv->counters.qtime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_CT_MAX:
+ secs = (double)sv->counters.ctime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_RT_MAX:
+ secs = (double)sv->counters.dtime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_TT_MAX:
+ secs = (double)sv->counters.ttime_max / 1000.0;
+ val = mkf_flt(FN_MAX, secs);
+ break;
+ case ST_F_CHECK_STATUS:
+ if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) != CHK_ST_ENABLED)
+ goto next_sv;
+
+ for (; ctx->obj_state < HCHK_STATUS_SIZE; ctx->obj_state++) {
+ if (get_check_status_result(ctx->obj_state) < CHK_RES_FAILED)
+ continue;
+ val = mkf_u32(FO_STATUS, sv->check.status == ctx->obj_state);
+ check_state = get_check_status_info(ctx->obj_state);
+ labels[2].name = ist("state");
+ labels[2].value = ist(check_state);
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ }
+ ctx->obj_state = 0;
+ goto next_sv;
+ case ST_F_CHECK_CODE:
+ if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) != CHK_ST_ENABLED)
+ goto next_sv;
+ val = mkf_u32(FN_OUTPUT, (sv->check.status < HCHK_STATUS_L57DATA) ? 0 : sv->check.code);
+ break;
+ case ST_F_CHECK_DURATION:
+ if (sv->check.status < HCHK_STATUS_CHECKED)
+ goto next_sv;
+ secs = (double)sv->check.duration / 1000.0;
+ val = mkf_flt(FN_DURATION, secs);
+ break;
+ case ST_F_REQ_TOT:
+ if (px->mode != PR_MODE_HTTP)
+ goto next_px;
+ val = stats[ctx->field_num];
+ break;
+ case ST_F_HRSP_1XX:
+ case ST_F_HRSP_2XX:
+ case ST_F_HRSP_3XX:
+ case ST_F_HRSP_4XX:
+ case ST_F_HRSP_5XX:
+ case ST_F_HRSP_OTHER:
+ if (px->mode != PR_MODE_HTTP)
+ goto next_px;
+ if (ctx->field_num != ST_F_HRSP_1XX)
+ ctx->flags &= ~PROMEX_FL_METRIC_HDR;
+ labels[2].name = ist("code");
+ labels[2].value = promex_hrsp_code[ctx->field_num - ST_F_HRSP_1XX];
+ val = stats[ctx->field_num];
+ break;
+
+ default:
+ val = stats[ctx->field_num];
+ }
+
+ if (!promex_dump_metric(appctx, htx, prefix, &promex_st_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+ next_sv:
+ ctx->sv = sv->next;
+ }
+
+ next_px:
+ ctx->px = px->next;
+ ctx->sv = (ctx->px ? ctx->px->srv : NULL);
+ }
+ ctx->flags |= PROMEX_FL_METRIC_HDR;
+ ctx->px = proxies_list;
+ ctx->sv = (ctx->px ? ctx->px->srv : NULL);
+ }
+
+
+ end:
+ if (out.len) {
+ if (!htx_add_data_atonce(htx, out))
+ return -1; /* Unexpected and unrecoverable error */
+ channel_add_input(chn, out.len);
+ }
+ return ret;
+ full:
+ ret = 0;
+ goto end;
+}
+
+/* Dump stick table metrics (prefixed by "haproxy_sticktable_"). It returns 1 on success,
+ * 0 if <htx> is full and -1 in case of any error. */
+static int promex_dump_sticktable_metrics(struct appctx *appctx, struct htx *htx)
+{
+ static struct ist prefix = IST("haproxy_sticktable_");
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct field val;
+ struct channel *chn = sc_ic(appctx_sc(appctx));
+ struct ist out = ist2(trash.area, 0);
+ size_t max = htx_get_max_blksz(htx, channel_htx_recv_max(chn, htx));
+ int ret = 1;
+ struct stktable *t;
+
+ for (; ctx->field_num < STICKTABLE_TOTAL_FIELDS; ctx->field_num++) {
+ if (!(promex_sticktable_metrics[ctx->field_num].flags & ctx->flags))
+ continue;
+
+ while (ctx->st) {
+ struct promex_label labels[PROMEX_MAX_LABELS - 1] = {};
+
+ t = ctx->st;
+ if (!t->size)
+ goto next_px;
+
+ labels[0].name = ist("name");
+ labels[0].value = ist2(t->id, strlen(t->id));
+ labels[1].name = ist("type");
+ labels[1].value = ist2(stktable_types[t->type].kw, strlen(stktable_types[t->type].kw));
+ switch (ctx->field_num) {
+ case STICKTABLE_SIZE:
+ val = mkf_u32(FN_GAUGE, t->size);
+ break;
+ case STICKTABLE_USED:
+ val = mkf_u32(FN_GAUGE, t->current);
+ break;
+ default:
+ goto next_px;
+ }
+
+ if (!promex_dump_metric(appctx, htx, prefix,
+ &promex_sticktable_metrics[ctx->field_num],
+ &val, labels, &out, max))
+ goto full;
+
+ next_px:
+ ctx->st = t->next;
+ }
+ ctx->flags |= PROMEX_FL_METRIC_HDR;
+ ctx->st = stktables_list;
+ }
+
+ end:
+ if (out.len) {
+ if (!htx_add_data_atonce(htx, out))
+ return -1; /* Unexpected and unrecoverable error */
+ channel_add_input(chn, out.len);
+ }
+ return ret;
+ full:
+ ret = 0;
+ goto end;
+}
+
+/* Dump all metrics (global, frontends, backends and servers) depending on the
+ * dumper state (appctx->st1). It returns 1 on success, 0 if <htx> is full and
+ * -1 in case of any error.
+ * Uses <appctx.ctx.stats.px> as a pointer to the current proxy and <sv>/<li>
+ * as pointers to the current server/listener respectively.
+ */
+static int promex_dump_metrics(struct appctx *appctx, struct stconn *sc, struct htx *htx)
+{
+ struct promex_ctx *ctx = appctx->svcctx;
+ int ret;
+
+ switch (appctx->st1) {
+ case PROMEX_DUMPER_INIT:
+ ctx->px = NULL;
+ ctx->st = NULL;
+ ctx->li = NULL;
+ ctx->sv = NULL;
+ ctx->flags |= (PROMEX_FL_METRIC_HDR|PROMEX_FL_INFO_METRIC);
+ ctx->obj_state = 0;
+ ctx->field_num = INF_NAME;
+ appctx->st1 = PROMEX_DUMPER_GLOBAL;
+ /* fall through */
+
+ case PROMEX_DUMPER_GLOBAL:
+ if (ctx->flags & PROMEX_FL_SCOPE_GLOBAL) {
+ ret = promex_dump_global_metrics(appctx, htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto full;
+ }
+ }
+
+ ctx->px = proxies_list;
+ ctx->st = NULL;
+ ctx->li = NULL;
+ ctx->sv = NULL;
+ ctx->flags &= ~PROMEX_FL_INFO_METRIC;
+ ctx->flags |= (PROMEX_FL_METRIC_HDR|PROMEX_FL_FRONT_METRIC);
+ ctx->obj_state = 0;
+ ctx->field_num = ST_F_PXNAME;
+ appctx->st1 = PROMEX_DUMPER_FRONT;
+ /* fall through */
+
+ case PROMEX_DUMPER_FRONT:
+ if (ctx->flags & PROMEX_FL_SCOPE_FRONT) {
+ ret = promex_dump_front_metrics(appctx, htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto full;
+ }
+ }
+
+ ctx->px = proxies_list;
+ ctx->st = NULL;
+ ctx->li = LIST_NEXT(&proxies_list->conf.listeners, struct listener *, by_fe);
+ ctx->sv = NULL;
+ ctx->flags &= ~PROMEX_FL_FRONT_METRIC;
+ ctx->flags |= (PROMEX_FL_METRIC_HDR|PROMEX_FL_LI_METRIC);
+ ctx->obj_state = 0;
+ ctx->field_num = ST_F_PXNAME;
+ appctx->st1 = PROMEX_DUMPER_LI;
+ /* fall through */
+
+ case PROMEX_DUMPER_LI:
+ if (ctx->flags & PROMEX_FL_SCOPE_LI) {
+ ret = promex_dump_listener_metrics(appctx, htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto full;
+ }
+ }
+
+ ctx->px = proxies_list;
+ ctx->st = NULL;
+ ctx->li = NULL;
+ ctx->sv = NULL;
+ ctx->flags &= ~PROMEX_FL_LI_METRIC;
+ ctx->flags |= (PROMEX_FL_METRIC_HDR|PROMEX_FL_BACK_METRIC);
+ ctx->obj_state = 0;
+ ctx->field_num = ST_F_PXNAME;
+ appctx->st1 = PROMEX_DUMPER_BACK;
+ /* fall through */
+
+ case PROMEX_DUMPER_BACK:
+ if (ctx->flags & PROMEX_FL_SCOPE_BACK) {
+ ret = promex_dump_back_metrics(appctx, htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto full;
+ }
+ }
+
+ ctx->px = proxies_list;
+ ctx->st = NULL;
+ ctx->li = NULL;
+ ctx->sv = ctx->px ? ctx->px->srv : NULL;
+ ctx->flags &= ~PROMEX_FL_BACK_METRIC;
+ ctx->flags |= (PROMEX_FL_METRIC_HDR|PROMEX_FL_SRV_METRIC);
+ ctx->obj_state = 0;
+ ctx->field_num = ST_F_PXNAME;
+ appctx->st1 = PROMEX_DUMPER_SRV;
+ /* fall through */
+
+ case PROMEX_DUMPER_SRV:
+ if (ctx->flags & PROMEX_FL_SCOPE_SERVER) {
+ ret = promex_dump_srv_metrics(appctx, htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto full;
+ }
+ }
+
+ ctx->px = NULL;
+ ctx->st = stktables_list;
+ ctx->li = NULL;
+ ctx->sv = NULL;
+ ctx->flags &= ~(PROMEX_FL_METRIC_HDR|PROMEX_FL_SRV_METRIC);
+ ctx->flags |= (PROMEX_FL_METRIC_HDR|PROMEX_FL_STICKTABLE_METRIC);
+ ctx->field_num = STICKTABLE_SIZE;
+ appctx->st1 = PROMEX_DUMPER_STICKTABLE;
+ /* fall through */
+
+ case PROMEX_DUMPER_STICKTABLE:
+ if (ctx->flags & PROMEX_FL_SCOPE_STICKTABLE) {
+ ret = promex_dump_sticktable_metrics(appctx, htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto full;
+ }
+ }
+
+ ctx->px = NULL;
+ ctx->st = NULL;
+ ctx->li = NULL;
+ ctx->sv = NULL;
+ ctx->flags &= ~(PROMEX_FL_METRIC_HDR|PROMEX_FL_STICKTABLE_METRIC);
+ ctx->field_num = 0;
+ appctx->st1 = PROMEX_DUMPER_DONE;
+ /* fall through */
+
+ case PROMEX_DUMPER_DONE:
+ default:
+ break;
+ }
+
+ return 1;
+
+ full:
+ sc_need_room(sc);
+ return 0;
+ error:
+ /* unrecoverable error */
+ ctx->px = NULL;
+ ctx->st = NULL;
+ ctx->li = NULL;
+ ctx->sv = NULL;
+ ctx->flags = 0;
+ ctx->field_num = 0;
+ appctx->st1 = PROMEX_DUMPER_DONE;
+ return -1;
+}
+
+/* Parse the query string of request URI to filter the metrics. It returns 1 on
+ * success and -1 on error. */
+static int promex_parse_uri(struct appctx *appctx, struct stconn *sc)
+{
+ struct promex_ctx *ctx = appctx->svcctx;
+ struct channel *req = sc_oc(sc);
+ struct channel *res = sc_ic(sc);
+ struct htx *req_htx, *res_htx;
+ struct htx_sl *sl;
+ char *p, *key, *value;
+ const char *end;
+ struct buffer *err;
+ int default_scopes = PROMEX_FL_SCOPE_ALL;
+ int len;
+
+ /* Get the query-string */
+ req_htx = htxbuf(&req->buf);
+ sl = http_get_stline(req_htx);
+ if (!sl)
+ goto error;
+ p = http_find_param_list(HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl), '?');
+ if (!p)
+ goto end;
+ end = HTX_SL_REQ_UPTR(sl) + HTX_SL_REQ_ULEN(sl);
+
+ /* copy the query-string */
+ len = end - p;
+ chunk_reset(&trash);
+ memcpy(trash.area, p, len);
+ trash.area[len] = 0;
+ p = trash.area;
+ end = trash.area + len;
+
+ /* Parse the query-string */
+ while (p < end && *p && *p != '#') {
+ value = NULL;
+
+ /* decode parameter name */
+ key = p;
+ while (p < end && *p != '=' && *p != '&' && *p != '#')
+ ++p;
+ /* found a value */
+ if (*p == '=') {
+ *(p++) = 0;
+ value = p;
+ }
+ else if (*p == '&')
+ *(p++) = 0;
+ else if (*p == '#')
+ *p = 0;
+ len = url_decode(key, 1);
+ if (len == -1)
+ goto error;
+
+ /* decode value */
+ if (value) {
+ while (p < end && *p != '=' && *p != '&' && *p != '#')
+ ++p;
+ if (*p == '=')
+ goto error;
+ if (*p == '&')
+ *(p++) = 0;
+ else if (*p == '#')
+ *p = 0;
+ len = url_decode(value, 1);
+ if (len == -1)
+ goto error;
+ }
+
+ if (strcmp(key, "scope") == 0) {
+ default_scopes = 0; /* at least a scope defined, unset default scopes */
+ if (!value)
+ goto error;
+ else if (*value == 0)
+ ctx->flags &= ~PROMEX_FL_SCOPE_ALL;
+ else if (*value == '*')
+ ctx->flags |= PROMEX_FL_SCOPE_ALL;
+ else if (strcmp(value, "global") == 0)
+ ctx->flags |= PROMEX_FL_SCOPE_GLOBAL;
+ else if (strcmp(value, "server") == 0)
+ ctx->flags |= PROMEX_FL_SCOPE_SERVER;
+ else if (strcmp(value, "backend") == 0)
+ ctx->flags |= PROMEX_FL_SCOPE_BACK;
+ else if (strcmp(value, "frontend") == 0)
+ ctx->flags |= PROMEX_FL_SCOPE_FRONT;
+ else if (strcmp(value, "listener") == 0)
+ ctx->flags |= PROMEX_FL_SCOPE_LI;
+ else if (strcmp(value, "sticktable") == 0)
+ ctx->flags |= PROMEX_FL_SCOPE_STICKTABLE;
+ else
+ goto error;
+ }
+ else if (strcmp(key, "no-maint") == 0)
+ ctx->flags |= PROMEX_FL_NO_MAINT_SRV;
+ }
+
+ end:
+ ctx->flags |= default_scopes;
+ return 1;
+
+ error:
+ err = &http_err_chunks[HTTP_ERR_400];
+ channel_erase(res);
+ res->buf.data = b_data(err);
+ memcpy(res->buf.area, b_head(err), b_data(err));
+ res_htx = htx_from_buf(&res->buf);
+ channel_add_input(res, res_htx->data);
+ appctx->st0 = PROMEX_ST_END;
+ return -1;
+}
+
+/* Send HTTP headers of the response. It returns 1 on success and 0 if <htx> is
+ * full. */
+static int promex_send_headers(struct appctx *appctx, struct stconn *sc, struct htx *htx)
+{
+ struct channel *chn = sc_ic(sc);
+ struct htx_sl *sl;
+ unsigned int flags;
+
+ flags = (HTX_SL_F_IS_RESP|HTX_SL_F_VER_11|HTX_SL_F_XFER_ENC|HTX_SL_F_XFER_LEN|HTX_SL_F_CHNK);
+ sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, ist("HTTP/1.1"), ist("200"), ist("OK"));
+ if (!sl)
+ goto full;
+ sl->info.res.status = 200;
+ if (!htx_add_header(htx, ist("Cache-Control"), ist("no-cache")) ||
+ !htx_add_header(htx, ist("Content-Type"), ist("text/plain; version=0.0.4")) ||
+ !htx_add_header(htx, ist("Transfer-Encoding"), ist("chunked")) ||
+ !htx_add_endof(htx, HTX_BLK_EOH))
+ goto full;
+
+ channel_add_input(chn, htx->data);
+ return 1;
+ full:
+ htx_reset(htx);
+ sc_need_room(sc);
+ return 0;
+}
+
+/* The function returns 1 if the initialisation is complete, 0 if
+ * an errors occurs and -1 if more data are required for initializing
+ * the applet.
+ */
+static int promex_appctx_init(struct appctx *appctx)
+{
+ applet_reserve_svcctx(appctx, sizeof(struct promex_ctx));
+ appctx->st0 = PROMEX_ST_INIT;
+ return 0;
+}
+
+/* The main I/O handler for the promex applet. */
+static void promex_appctx_handle_io(struct appctx *appctx)
+{
+ struct stconn *sc = appctx_sc(appctx);
+ struct stream *s = __sc_strm(sc);
+ struct channel *req = sc_oc(sc);
+ struct channel *res = sc_ic(sc);
+ struct htx *req_htx, *res_htx;
+ int ret;
+
+ res_htx = htx_from_buf(&res->buf);
+ if (unlikely(sc->state == SC_ST_DIS || sc->state == SC_ST_CLO))
+ goto out;
+
+ /* Check if the input buffer is available. */
+ if (!b_size(&res->buf)) {
+ sc_need_room(sc);
+ goto out;
+ }
+
+ switch (appctx->st0) {
+ case PROMEX_ST_INIT:
+ ret = promex_parse_uri(appctx, sc);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto out;
+ }
+ appctx->st0 = PROMEX_ST_HEAD;
+ appctx->st1 = PROMEX_DUMPER_INIT;
+ /* fall through */
+
+ case PROMEX_ST_HEAD:
+ if (!promex_send_headers(appctx, sc, res_htx))
+ goto out;
+ appctx->st0 = ((s->txn->meth == HTTP_METH_HEAD) ? PROMEX_ST_DONE : PROMEX_ST_DUMP);
+ /* fall through */
+
+ case PROMEX_ST_DUMP:
+ ret = promex_dump_metrics(appctx, sc, res_htx);
+ if (ret <= 0) {
+ if (ret == -1)
+ goto error;
+ goto out;
+ }
+ appctx->st0 = PROMEX_ST_DONE;
+ /* fall through */
+
+ case PROMEX_ST_DONE:
+ /* no more data are expected. If the response buffer is
+ * empty, be sure to add something (EOT block in this
+ * case) to have something to send. It is important to
+ * be sure the EOM flags will be handled by the
+ * endpoint.
+ */
+ if (htx_is_empty(res_htx)) {
+ if (!htx_add_endof(res_htx, HTX_BLK_EOT)) {
+ sc_need_room(sc);
+ goto out;
+ }
+ channel_add_input(res, 1);
+ }
+ res_htx->flags |= HTX_FL_EOM;
+ res->flags |= CF_EOI;
+ se_fl_set(appctx->sedesc, SE_FL_EOI);
+ appctx->st0 = PROMEX_ST_END;
+ /* fall through */
+
+ case PROMEX_ST_END:
+ if (!(res->flags & CF_SHUTR)) {
+ res->flags |= CF_READ_NULL;
+ sc_shutr(sc);
+ }
+ }
+
+ out:
+ htx_to_buf(res_htx, &res->buf);
+
+ /* eat the whole request */
+ if (co_data(req)) {
+ req_htx = htx_from_buf(&req->buf);
+ co_htx_skip(req, req_htx, co_data(req));
+ }
+ return;
+
+ error:
+ res->flags |= CF_READ_NULL;
+ sc_shutr(sc);
+ sc_shutw(sc);
+ goto out;
+}
+
+struct applet promex_applet = {
+ .obj_type = OBJ_TYPE_APPLET,
+ .name = "<PROMEX>", /* used for logging */
+ .init = promex_appctx_init,
+ .fct = promex_appctx_handle_io,
+};
+
+static enum act_parse_ret service_parse_prometheus_exporter(const char **args, int *cur_arg, struct proxy *px,
+ struct act_rule *rule, char **err)
+{
+ /* Prometheus exporter service is only available on "http-request" rulesets */
+ if (rule->from != ACT_F_HTTP_REQ) {
+ memprintf(err, "Prometheus exporter service only available on 'http-request' rulesets");
+ return ACT_RET_PRS_ERR;
+ }
+
+ /* Add applet pointer in the rule. */
+ rule->applet = promex_applet;
+
+ return ACT_RET_PRS_OK;
+}
+static void promex_register_build_options(void)
+{
+ char *ptr = NULL;
+
+ memprintf(&ptr, "Built with the Prometheus exporter as a service");
+ hap_register_build_opts(ptr, 1);
+}
+
+
+static struct action_kw_list service_actions = { ILH, {
+ { "prometheus-exporter", service_parse_prometheus_exporter },
+ { /* END */ }
+}};
+
+INITCALL1(STG_REGISTER, service_keywords_register, &service_actions);
+INITCALL0(STG_REGISTER, promex_register_build_options);
diff --git a/addons/wurfl/dummy/Makefile b/addons/wurfl/dummy/Makefile
new file mode 100644
index 0000000..df08288
--- /dev/null
+++ b/addons/wurfl/dummy/Makefile
@@ -0,0 +1,13 @@
+# makefile for dummy wurfl library
+# builds shared library
+# installs it in /usr/lib/ with header file wurfl.h in /usr/include/wurfl
+#
+# install needs to be run as root
+
+build: libwurfl.a
+
+libwurfl.a: dummy-wurfl.o
+ ar rv $@ $<
+
+clean:
+ rm -rf *.a *.o
diff --git a/addons/wurfl/dummy/dummy-wurfl.c b/addons/wurfl/dummy/dummy-wurfl.c
new file mode 100644
index 0000000..0d5f068
--- /dev/null
+++ b/addons/wurfl/dummy/dummy-wurfl.c
@@ -0,0 +1,126 @@
+/*
+ * InFuze C API - HAPROXY Dummy library version of include
+ *
+ * Author : Paul Stephen Borile, Mon Apr 8, 2019
+ * Copyright (c) ScientiaMobile, Inc.
+ * http://www.scientiamobile.com
+ *
+ * This is a dummy implementation of the wurfl C API that builds and runs
+ * like the normal API simply without returning device detection data
+ *
+ *
+ */
+
+#include "wurfl/wurfl.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+
+const char *wurfl_get_api_version(void)
+{
+ return "1.11.2.100"; // 100 indicates the dummy
+}
+
+wurfl_handle wurfl_create(void)
+{
+ return (void*) 0xbeffa;
+}
+
+void wurfl_destroy(wurfl_handle handle)
+{
+ return;
+}
+
+wurfl_error wurfl_set_root(wurfl_handle hwurfl, const char* root)
+{
+ return WURFL_OK;
+}
+wurfl_error wurfl_add_patch(wurfl_handle hwurfl, const char *patch)
+{
+ return WURFL_OK;
+}
+
+wurfl_error wurfl_add_requested_capability(wurfl_handle hwurfl, const char *requested_capability)
+{
+ return WURFL_OK;
+}
+
+const char *wurfl_get_error_message(wurfl_handle hwurfl)
+{
+ return "wurfl dummy library error message";
+}
+
+int wurfl_has_virtual_capability(wurfl_handle hwurfl, const char *virtual_capability)
+{
+ return 0;
+}
+
+wurfl_error wurfl_set_cache_provider(wurfl_handle hwurfl, wurfl_cache_provider cache_provider, const char *config)
+{
+ return WURFL_OK;
+}
+
+wurfl_error wurfl_load(wurfl_handle hwurfl)
+{
+ return WURFL_OK;
+}
+
+wurfl_device_handle wurfl_lookup(wurfl_handle hwurfl, wurfl_header_retrieve_callback header_retrieve_callback, const void *header_retrieve_callback_data)
+{
+ // call callback, on a probably existing header
+ const char *hvalue = header_retrieve_callback("User-Agent", header_retrieve_callback_data);
+ // and on a non existing one
+ hvalue = header_retrieve_callback("Non-Existing-Header", header_retrieve_callback_data);
+ (void)hvalue;
+ return (void *) 0xdeffa;
+}
+
+const char *wurfl_device_get_capability(wurfl_device_handle hwurfldevice, const char *capability)
+{
+ return "dummy_cap_val";
+}
+
+const char *wurfl_device_get_virtual_capability(wurfl_device_handle hwurfldevice, const char *capability)
+{
+ return "dummy_vcap_val";
+}
+
+void wurfl_device_destroy(wurfl_device_handle handle)
+{
+ return;
+}
+
+const char *wurfl_device_get_id(wurfl_device_handle hwurfldevice)
+{
+ return "generic_dummy_device";
+}
+
+const char *wurfl_device_get_root_id(wurfl_device_handle hwurfldevice)
+{
+ return "generic_dummy_device";
+}
+
+const char *wurfl_device_get_original_useragent(wurfl_device_handle hwurfldevice)
+{
+ return "original_useragent";
+}
+const char *wurfl_device_get_normalized_useragent(wurfl_device_handle hwurfldevice)
+{
+ return "normalized_useragent";
+}
+int wurfl_device_is_actual_device_root(wurfl_device_handle hwurfldevice)
+{
+ return 1;
+}
+
+const char *wurfl_get_wurfl_info(wurfl_handle hwurfl)
+{
+ return "dummy wurfl info";
+}
+
+const char *wurfl_get_last_load_time_as_string(wurfl_handle hwurfl)
+{
+ return "dummy wurfl last load time";
+}
+
+#pragma GCC diagnostic pop
diff --git a/addons/wurfl/dummy/wurfl/wurfl.h b/addons/wurfl/dummy/wurfl/wurfl.h
new file mode 100644
index 0000000..7659561
--- /dev/null
+++ b/addons/wurfl/dummy/wurfl/wurfl.h
@@ -0,0 +1,409 @@
+/*
+ * InFuze C API - HAPROXY Dummy library version of include
+ *
+ * Copyright (c) ScientiaMobile, Inc.
+ * http://www.scientiamobile.com
+ *
+ * This software package is the property of ScientiaMobile Inc. and is distributed under
+ * a dual licensing scheme:
+ *
+ * 1) commercially according to a contract between the Licensee and ScientiaMobile Inc. (Licensor).
+ * If you represent the Licensee, please refer to the licensing agreement which has been signed
+ * between the two parties. If you do not represent the Licensee, you are not authorized to use
+ * this software in any way.
+ *
+ * 2) LGPL when used in the context of the HAProxy project with the purpose of testing compatibility
+ * of HAProxy with ScientiaMobile software.
+ *
+ */
+
+#ifndef _WURFL_H_
+#define _WURFL_H_
+
+#include <time.h>
+
+#if defined (__GNUC__) || defined (__clang__)
+#define DEPRECATED __attribute__((deprecated))
+#elif defined(_MSC_VER)
+#define DEPRECATED __declspec(deprecated)
+#else
+#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
+#define DEPRECATED
+#endif
+
+// WURFL error enumeration
+typedef enum {
+ WURFL_OK = 0, //!< no error
+ WURFL_ERROR_INVALID_HANDLE = 1, //!< handle passed to the function is invalid
+ WURFL_ERROR_ALREADY_LOAD = 2, //!< wurfl_load has already been invoked on the specific wurfl_handle
+ WURFL_ERROR_FILE_NOT_FOUND = 3, //!< file not found during wurfl_load or remote data file update
+ WURFL_ERROR_UNEXPECTED_END_OF_FILE = 4, //!< unexpected end of file or parsing error during wurfl_load
+ WURFL_ERROR_INPUT_OUTPUT_FAILURE = 5, //!< error reading stream during wurfl_load or updater accessing local updated data file
+ WURFL_ERROR_DEVICE_NOT_FOUND = 6, //!< specified device is missing
+ WURFL_ERROR_CAPABILITY_NOT_FOUND = 7, //!< specified capability is missing
+ WURFL_ERROR_INVALID_CAPABILITY_VALUE = 8, //!< invalid capability value
+ WURFL_ERROR_VIRTUAL_CAPABILITY_NOT_FOUND = 9, //!< specified virtual capability is missing
+ WURFL_ERROR_CANT_LOAD_CAPABILITY_NOT_FOUND = 10, //!< specified capability is missing
+ WURFL_ERROR_CANT_LOAD_VIRTUAL_CAPABILITY_NOT_FOUND = 11, //!< specified virtual capability is missing
+ WURFL_ERROR_EMPTY_ID = 12, //!< missing id in searching device
+ WURFL_ERROR_CAPABILITY_GROUP_NOT_FOUND = 13, //!< specified capability is missing in its group
+ WURFL_ERROR_CAPABILITY_GROUP_MISMATCH = 14, //!< specified capability mismatch in its group
+ WURFL_ERROR_DEVICE_ALREADY_DEFINED = 15, //!< specified device is already defined
+ WURFL_ERROR_USERAGENT_ALREADY_DEFINED = 16, //!< specified user agent is already defined
+ WURFL_ERROR_DEVICE_HIERARCHY_CIRCULAR_REFERENCE = 17, //!< circular reference in device hierarchy
+ WURFL_ERROR_UNKNOWN = 18, //!< unknown error
+ WURFL_ERROR_INVALID_USERAGENT_PRIORITY = 19, //!< specified override sideloaded browser user agent configuration not valid
+ WURFL_ERROR_INVALID_PARAMETER = 20, //!< invalid parameter
+ WURFL_ERROR_INVALID_CACHE_SIZE = 21, //!< specified an invalid cache size, 0 or a negative value.
+ WURFL_ERROR_XML_CONSISTENCY = 22, //!< WURFL data file is out of date or wrong - some needed device_id/capability is missing
+ WURFL_ERROR_INTERNAL = 23, //!< internal error. If this is an updater issue, please enable and check updater log using wurfl_updater_set_log_path()
+ WURFL_ERROR_VIRTUAL_CAPABILITY_NOT_AVAILABLE = 24, //!< the requested virtual capability has not been licensed
+ WURFL_ERROR_MISSING_USERAGENT = 25, // an XML device definition without mandatory UA has been detected
+ WURFL_ERROR_XML_PARSE = 26, // the XML data file is malformed
+ WURFL_ERROR_UPDATER_INVALID_DATA_URL = 27, // updater data URL is missing or invalid (note: only .zip and .gz formats allowed)
+ WURFL_ERROR_UPDATER_INVALID_LICENSE = 28, // client license is invalid, expired etc
+ WURFL_ERROR_UPDATER_NETWORK_ERROR = 29, // updater request returned an HTTP response != 200, or SSL error, etc. Please enable and check updater log using wurfl_updater_set_log_path()
+ WURFL_ERROR_ENGINE_NOT_INITIALIZED = 30, // prerequisite for executing an update is that the engine has been initialized (i.e., wurfl_load() has been called)
+ WURFL_ERROR_UPDATER_ALREADY_RUNNING = 31, // wurfl_updater_start() can be called just once, when the updater is not running
+ WURFL_ERROR_UPDATER_NOT_RUNNING = 32, // wurfl_updater_stop() can be called just once, when the updater is running
+ WURFL_ERROR_UPDATER_TOO_MANY_REQUESTS = 33, // Updater encountered HTTP 429 error
+ WURFL_ERROR_UPDATER_CMDLINE_DOWNLOADER_UNAVAILABLE = 34, // Curl executable not found. Please check path, etc
+ WURFL_ERROR_UPDATER_TIMEDOUT = 35, // Curl operation timed out.
+ WURFL_ERROR_ROOT_NOT_SET = 36, // set_root() must be called before any load() / reload() and update attempt
+ WURFL_ERROR_WRONG_ENGINE_TARGET = 37, // set_engine_target() was called with a wrong/unrecognized parameter
+ // new errors added in
+
+ WURFL_ERROR_CANNOT_FILTER_STATIC_CAP = 38,
+ WURFL_ENGINE_UNABLE_TO_ALLOCATE_MEMORY = 39,
+ WURFL_ENGINE_NOT_LOADED = 40,
+ WURFL_ERROR_UPDATER_CANNOT_START_THREAD = 41,
+ WURFL_ERROR_ENUM_EMPTY_SET = 42,
+
+ // update when adding errors
+ WURFL_ERROR_LAST = 43
+} wurfl_error;
+
+typedef enum {
+ WURFL_ENGINE_TARGET_HIGH_ACCURACY = 0,
+ WURFL_ENGINE_TARGET_HIGH_PERFORMANCE = 1,
+ WURFL_ENGINE_TARGET_DEFAULT = 2,
+ WURFL_ENGINE_TARGET_FAST_DESKTOP_BROWSER_MATCH = 3,
+} wurfl_engine_target;
+
+typedef enum {
+ WURFL_USERAGENT_PRIORITY_OVERRIDE_SIDELOADED_BROWSER_USERAGENT,
+ WURFL_USERAGENT_PRIORITY_USE_PLAIN_USERAGENT,
+ WURFL_USERAGENT_PRIORITY_INVALID,
+} wurfl_useragent_priority;
+
+typedef enum {
+ WURFL_CACHE_PROVIDER_NONE,
+ WURFL_CACHE_PROVIDER_LRU,
+ WURFL_CACHE_PROVIDER_DOUBLE_LRU,
+} wurfl_cache_provider;
+
+typedef enum {
+ WURFL_MATCH_TYPE_EXACT = 0,
+ WURFL_MATCH_TYPE_CONCLUSIVE = 1,
+ WURFL_MATCH_TYPE_RECOVERY = 2,
+ WURFL_MATCH_TYPE_CATCHALL = 3,
+ WURFL_MATCH_TYPE_HIGHPERFORMANCE = 4, // deprecated. See hereunder.
+ WURFL_MATCH_TYPE_NONE = 5,
+ WURFL_MATCH_TYPE_CACHED = 6,
+ WURFL_MATCH_TYPE_FAST_DESKTOP_BROWSER_MATCH = 7
+} wurfl_match_type;
+
+
+typedef enum {
+ WURFL_UPDATER_FREQ_DAILY = 0,
+ WURFL_UPDATER_FREQ_WEEKLY = 1,
+} wurfl_updater_frequency;
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// typedef struct _we_h * wurfl_handle;
+// typedef struct _en_t * wurfl_enum_handle;
+// typedef struct _en_t * wurfl_device_capability_enumerator_handle;
+// typedef struct _en_t * wurfl_capability_enumerator_handle;
+// typedef struct _en_t * wurfl_device_id_enumerator_handle;
+// typedef struct _md_t * wurfl_device_handle;
+
+typedef void * wurfl_handle;
+typedef void * wurfl_enum_handle;
+typedef void * wurfl_device_capability_enumerator_handle;
+typedef void * wurfl_capability_enumerator_handle;
+typedef void * wurfl_device_id_enumerator_handle;
+typedef void * wurfl_device_handle;
+
+const char *wurfl_get_api_version(void);
+wurfl_handle wurfl_create(void);
+void wurfl_destroy(wurfl_handle handle);
+
+// NEW : enable/set api logfile
+wurfl_error wurfl_set_log_path(wurfl_handle hwurfl, const char *log_path);
+// allow writing user stuff on logs : mesg will be prepended by a "USER LOG :" string
+wurfl_error wurfl_log_print(wurfl_handle hwurfl, char *msg);
+
+// Errors
+
+const char *wurfl_get_error_message(wurfl_handle hwurfl);
+wurfl_error wurfl_get_error_code(wurfl_handle hwurfl);
+int wurfl_has_error_message(wurfl_handle hwurfl);
+// deprecated
+void wurfl_clear_error_message(wurfl_handle hwurfl);
+
+const char *wurfl_get_wurfl_info(wurfl_handle hwurfl);
+wurfl_error wurfl_set_root(wurfl_handle hwurfl, const char* root);
+wurfl_error wurfl_add_patch(wurfl_handle hwurfl, const char *patch);
+wurfl_error wurfl_add_requested_capability(wurfl_handle hwurfl, const char *requested_capability);
+DEPRECATED wurfl_error wurfl_set_engine_target(wurfl_handle hwurfl, wurfl_engine_target target);
+DEPRECATED wurfl_engine_target wurfl_get_engine_target(wurfl_handle hwurfl);
+DEPRECATED const char *wurfl_get_engine_target_as_string(wurfl_handle hwurfl);
+DEPRECATED wurfl_error wurfl_set_useragent_priority(wurfl_handle hwurfl, wurfl_useragent_priority useragent_priority);
+DEPRECATED wurfl_useragent_priority wurfl_get_useragent_priority(wurfl_handle hwurfl);
+DEPRECATED const char *wurfl_get_useragent_priority_as_string(wurfl_handle hwurfl);
+wurfl_error wurfl_set_cache_provider(wurfl_handle hwurfl, wurfl_cache_provider cache_provider, const char *config);
+wurfl_error wurfl_load(wurfl_handle hwurfl);
+struct tm *wurfl_get_last_load_time(wurfl_handle hwurfl);
+const char *wurfl_get_last_load_time_as_string(wurfl_handle hwurfl);
+int wurfl_has_capability(wurfl_handle hwurfl, const char *capability);
+int wurfl_has_virtual_capability(wurfl_handle hwurfl, const char *virtual_capability);
+
+/*
+ * enumerators
+ */
+
+/*
+ * new enumerators implementation
+ *
+ * a selector is used to indicate which enumerator we needed
+ WURFL_ENUM_VIRTUAL_CAPABILITIES, WURFL_ENUM_STATIC_CAPABILITIES, WURFL_ENUM_MANDATORY_CAPABILITIES, WURFL_ENUM_WURFLID,
+ */
+
+typedef enum {
+ WURFL_ENUM_STATIC_CAPABILITIES,
+ WURFL_ENUM_VIRTUAL_CAPABILITIES,
+ WURFL_ENUM_MANDATORY_CAPABILITIES,
+ WURFL_ENUM_WURFLID,
+} wurfl_enum_type;
+
+wurfl_enum_handle wurfl_enum_create(wurfl_handle, wurfl_enum_type);
+const char *wurfl_enum_get_name(wurfl_enum_handle handle);
+int wurfl_enum_is_valid(wurfl_enum_handle handle);
+void wurfl_enum_move_next(wurfl_enum_handle handle);
+void wurfl_enum_destroy(wurfl_enum_handle handle);
+
+/* deprecated enumerators */
+// virtual caps
+//DEPRECATED wurfl_capability_enumerator_handle wurfl_get_virtual_capability_enumerator(wurfl_handle hwurfl);
+wurfl_capability_enumerator_handle wurfl_get_virtual_capability_enumerator(wurfl_handle hwurfl);
+
+// all mandatories
+//DEPRECATED wurfl_capability_enumerator_handle wurfl_get_mandatory_capability_enumerator(wurfl_handle hwurfl);
+wurfl_capability_enumerator_handle wurfl_get_mandatory_capability_enumerator(wurfl_handle hwurfl);
+
+// all capabilities
+//DEPRECATED wurfl_capability_enumerator_handle wurfl_get_capability_enumerator(wurfl_handle hwurfl);
+wurfl_capability_enumerator_handle wurfl_get_capability_enumerator(wurfl_handle hwurfl);
+//DEPRECATED const char *wurfl_capability_enumerator_get_name(wurfl_capability_enumerator_handle hwurflcapabilityenumeratorhandle);
+const char *wurfl_capability_enumerator_get_name(wurfl_capability_enumerator_handle hwurflcapabilityenumeratorhandle);
+//DEPRECATED int wurfl_capability_enumerator_is_valid(wurfl_capability_enumerator_handle handle);
+int wurfl_capability_enumerator_is_valid(wurfl_capability_enumerator_handle handle);
+//DEPRECATED void wurfl_capability_enumerator_move_next(wurfl_capability_enumerator_handle handle);
+void wurfl_capability_enumerator_move_next(wurfl_capability_enumerator_handle handle);
+//DEPRECATED void wurfl_capability_enumerator_destroy(wurfl_capability_enumerator_handle handle);
+void wurfl_capability_enumerator_destroy(wurfl_capability_enumerator_handle handle);
+
+// device id enumerator
+//DEPRECATED wurfl_device_id_enumerator_handle wurfl_get_device_id_enumerator(wurfl_handle hwurfl);
+wurfl_device_id_enumerator_handle wurfl_get_device_id_enumerator(wurfl_handle hwurfl);
+//DEPRECATED const char *wurfl_device_id_enumerator_get_device_id(wurfl_device_id_enumerator_handle hwurfldeviceidenumeratorhandle);
+const char *wurfl_device_id_enumerator_get_device_id(wurfl_device_id_enumerator_handle hwurfldeviceidenumeratorhandle);
+//DEPRECATED int wurfl_device_id_enumerator_is_valid(wurfl_device_id_enumerator_handle handle);
+int wurfl_device_id_enumerator_is_valid(wurfl_device_id_enumerator_handle handle);
+//DEPRECATED void wurfl_device_id_enumerator_move_next(wurfl_device_id_enumerator_handle handle);
+void wurfl_device_id_enumerator_move_next(wurfl_device_id_enumerator_handle handle);
+//DEPRECATED void wurfl_device_id_enumerator_destroy(wurfl_device_id_enumerator_handle handle);
+void wurfl_device_id_enumerator_destroy(wurfl_device_id_enumerator_handle handle);
+
+/*
+ * deprecated device enumerators
+ */
+
+//DEPRECATED wurfl_device_capability_enumerator_handle wurfl_device_get_capability_enumerator(wurfl_device_handle hwurfldevice);
+wurfl_device_capability_enumerator_handle wurfl_device_get_capability_enumerator(wurfl_device_handle hwurfldevice);
+//DEPRECATED wurfl_device_capability_enumerator_handle wurfl_device_get_virtual_capability_enumerator(wurfl_device_handle hwurfldevice);
+wurfl_device_capability_enumerator_handle wurfl_device_get_virtual_capability_enumerator(wurfl_device_handle hwurfldevice);
+//DEPRECATED const char *wurfl_device_capability_enumerator_get_name(wurfl_device_capability_enumerator_handle);
+const char *wurfl_device_capability_enumerator_get_name(wurfl_device_capability_enumerator_handle);
+//DEPRECATED int wurfl_device_capability_enumerator_is_valid(wurfl_device_capability_enumerator_handle);
+int wurfl_device_capability_enumerator_is_valid(wurfl_device_capability_enumerator_handle);
+//DEPRECATED void wurfl_device_capability_enumerator_move_next(wurfl_device_capability_enumerator_handle);
+void wurfl_device_capability_enumerator_move_next(wurfl_device_capability_enumerator_handle);
+//DEPRECATED void wurfl_device_capability_enumerator_destroy(wurfl_device_capability_enumerator_handle);
+void wurfl_device_capability_enumerator_destroy(wurfl_device_capability_enumerator_handle);
+
+//DEPRECATED const char *wurfl_device_capability_enumerator_get_value(wurfl_device_capability_enumerator_handle);
+const char *wurfl_device_capability_enumerator_get_value(wurfl_device_capability_enumerator_handle);
+//DEPRECATED int wurfl_device_capability_enumerator_get_value_as_int(wurfl_device_capability_enumerator_handle hwurfldevicecapabilityenumeratorhandle);
+int wurfl_device_capability_enumerator_get_value_as_int(wurfl_device_capability_enumerator_handle hwurfldevicecapabilityenumeratorhandle);
+//DEPRECATED int wurfl_device_capability_enumerator_get_value_as_bool(wurfl_device_capability_enumerator_handle hwurfldevicecapabilityenumeratorhandle);
+int wurfl_device_capability_enumerator_get_value_as_bool(wurfl_device_capability_enumerator_handle hwurfldevicecapabilityenumeratorhandle);
+
+
+/*
+ * Device lookup methods
+ */
+
+typedef const char *(*wurfl_header_retrieve_callback)(const char *header_name, const void *callback_data);
+
+wurfl_device_handle wurfl_lookup(wurfl_handle hwurfl, wurfl_header_retrieve_callback header_retrieve_callback, const void *header_retrieve_callback_data);
+wurfl_device_handle wurfl_lookup_useragent(wurfl_handle hwurfl, const char *useragent);
+wurfl_device_handle wurfl_get_device(wurfl_handle hwurfl, const char *deviceid);
+wurfl_device_handle wurfl_get_device_with_headers(wurfl_handle hwurfl, const char *deviceid, wurfl_header_retrieve_callback header_retrieve_callback, const void *header_retrieve_callback_data);
+
+/*
+ * device related methods
+ */
+
+const char *wurfl_device_get_id(wurfl_device_handle hwurfldevice);
+const char *wurfl_device_get_root_id(wurfl_device_handle hwurfldevice);
+const char *wurfl_device_get_useragent(wurfl_device_handle hwurfldevice);
+const char *wurfl_device_get_original_useragent(wurfl_device_handle hwurfldevice);
+const char *wurfl_device_get_normalized_useragent(wurfl_device_handle hwurfldevice);
+int wurfl_device_is_actual_device_root(wurfl_device_handle hwurfldevice);
+wurfl_match_type wurfl_device_get_match_type(wurfl_device_handle hwurfldevice);
+const char *wurfl_device_get_matcher_name(wurfl_device_handle hwurfldevice);
+const char *wurfl_device_get_bucket_matcher_name(wurfl_device_handle hwurfldevice);
+void wurfl_device_destroy(wurfl_device_handle handle);
+
+
+/*
+ * static capability, virtual capability methods
+ */
+
+int wurfl_device_has_capability(wurfl_device_handle hwurfldevice, const char *capability);
+
+const char *wurfl_device_get_capability(wurfl_device_handle hwurfldevice, const char *capability);
+int wurfl_device_get_capability_as_int(wurfl_device_handle hwurfldevice, const char *capability);
+int wurfl_device_get_capability_as_bool(wurfl_device_handle hwurfldevice, const char *capability);
+
+int wurfl_device_has_virtual_capability(wurfl_device_handle hwurfldevice, const char *capability);
+
+const char *wurfl_device_get_virtual_capability(wurfl_device_handle hwurfldevice, const char *capability);
+int wurfl_device_get_virtual_capability_as_int(wurfl_device_handle hwurfldevice, const char *capability);
+int wurfl_device_get_virtual_capability_as_bool(wurfl_device_handle hwurfldevice, const char *capability);
+
+/*
+ * static capability, virtual capability NEW methods
+ */
+
+const char *wurfl_device_get_static_cap(wurfl_device_handle hwdev, const char *cap, wurfl_error *err);
+int wurfl_device_get_static_cap_as_int(wurfl_device_handle hwdev, const char *cap, wurfl_error *err);
+int wurfl_device_get_static_cap_as_bool(wurfl_device_handle hwdev, const char *cap, wurfl_error *err);
+
+const char *wurfl_device_get_virtual_cap(wurfl_device_handle hwdev, const char *vcap, wurfl_error *err);
+int wurfl_device_get_virtual_cap_as_int(wurfl_device_handle hwdev, const char *vcap, wurfl_error *err);
+int wurfl_device_get_virtual_cap_as_bool(wurfl_device_handle hwdev, const char *vcap, wurfl_error *err);
+
+/*
+ * Updater methods
+ */
+
+// Instruct the updater module to log to file any operation/error. If not used, the updater will not log anything.
+// Returns: WURLF_OK if no errors, WURFL_ERROR_INPUT_OUTPUT_FAILURE if the log file cannot be created (no write access rights?)
+// or if you try to reopen the log file anywhere else, i.e. this call can be made just once, any attempt to reopen a different log file will fail.
+wurfl_error wurfl_updater_set_log_path(wurfl_handle hwurfl, const char *log_path);
+
+// Set remote data file URL for downloading via internal updater. Will execute various validation tests
+// eventually returning WURFL_ERROR_UPDATER_XXX errors for various error conditions and logging detailed infos if
+// update logger is enabled.
+wurfl_error wurfl_updater_set_data_url(wurfl_handle hwurfl, const char *data_url);
+
+// Set the updater frequency of automatic updates. Will run a background task with given update frequency.
+wurfl_error wurfl_updater_set_data_frequency(wurfl_handle hwurfl, wurfl_updater_frequency freq);
+
+// Set updater timeouts.
+// There are two timeouts, both in milliseconds : connection timeout and operation timeout.
+// The values are mapped to CURL --connect-timeout and --max-time parameters
+// (after millisecs-to-secs conversion). Note that CURL sub millisecond timeouts don't work for
+// lack of a way to specify decimal values for timeout to curl (using 0.05 for example fails to work
+// on docker machines with "POSIX" locale installed.
+// Connection timeout has a default value of 10 seconds (10000 ms) and refers only to connection phase. Passing 0 will use CURL value "no timeout used".
+// Data transfer timeout has a default value of 600 seconds (600000 ms). Passing 0 will use CURL default value "no timeout used"
+// So, pass 0 to either parameter to set it to "no timeout used"
+// Pass -1 to either parameter to use default values (10 secs, 600 secs)
+// The specified timeouts (if any) are used just in the synchronous (i.e., wurfl_updater_runonce()) API call.
+// The asynchronous background updater always runs with default (CURL) timeouts (i.e., it will wait "as long as needed" for a new data file to be downloaded)
+wurfl_error wurfl_updater_set_data_url_timeouts(wurfl_handle hwurfl, int connection_timeout, int data_transfer_timeout);
+
+// Call a synchronous update. This is a blocking call and will execute the whole process
+// of downloading the new data file, checking for correctness, replacing the data file and restarting the engine.
+// Will keep all old configurations (patches, cache, etc)
+// Returns WURLF_OK if no errors,
+// or WURFL_ERROR_UPDATER_XXX errors for various error conditions, eventually logging detailed infos if
+// update logger is enabled.
+wurfl_error wurfl_updater_runonce(wurfl_handle hwurfl);
+
+// Start the asynchronous update thread. Can be called just once when the updater is stopped;
+// Subsequent/wrong calls will return WURFL_ERROR_UPDATER_ALREADY_RUNNING
+// Will also return WURFL_ERROR_UPDATER_XXX errors for various initialization error conditions (see above), eventually logging detailed infos if
+// update logger is enabled.
+// On success will return WURLF_OK
+wurfl_error wurfl_updater_start(wurfl_handle hwurfl);
+
+// Stop the asynchronous update thread. Can be called just once when the updater is started;
+// Subsequent/wrong calls will return WURFL_ERROR_UPDATER_NOT_RUNNING.
+// On success will return WURLF_OK
+wurfl_error wurfl_updater_stop(wurfl_handle hwurfl);
+
+// Reload and reboot the engine with the given data file. Basically, the same process of a wurfl_updater_runonce but without the file download.
+// Will synchronously load the new root testing for errors, restart the engine with the new data file and overwrite the old data file with the new one.
+// Will keep old configuration (patches, cache, etc)
+// Preconditions: wurfl_set_root() and wurfl_load() must have been called and the new root must be of the same kind (i.e, same extension) as the actual root
+// You can force a reload of the actual set_root() file passing NULL as the newroot
+wurfl_error wurfl_updater_reload_root(wurfl_handle hwurfl, const char *newroot);
+
+// Alternative API for passing headers to lookup functions
+
+// An opaque type representing a name/value headers map
+// You can create, fill and destroy this object directly.
+typedef struct _ih_h * wurfl_important_header_handle;
+wurfl_important_header_handle wurfl_important_header_create(wurfl_handle);
+wurfl_error wurfl_important_header_set(wurfl_important_header_handle, const char *name, const char *value);
+void wurfl_important_header_destroy(wurfl_important_header_handle);
+
+// Alternative lookup functions using the above wurfl_important_header_handle object.
+// Once called, you can destroy the wurfl_important_header_handle object. Headers values are cached internally in the wurfl_device_handle.
+wurfl_device_handle wurfl_lookup_with_important_header(wurfl_handle, wurfl_important_header_handle);
+wurfl_device_handle wurfl_get_device_with_important_header(wurfl_handle, const char *deviceid, wurfl_important_header_handle);
+
+// Enumerator of all headers that should be passed to a lookup function. Returns a null-termninated list of const char*
+//
+// Example usage:
+//
+// const char** importantHeadersNames = wurfl_get_important_header_names();
+// int i = 0;
+// while (importantHeadersNames[i])
+// {
+// printf("important header %i: %s\n", i, headerNames[i]);
+// i++;
+// }
+const char **wurfl_get_important_header_names(void);
+
+// classic WURFL iterator version of the enumerator hereabove.
+typedef void *wurfl_important_header_enumerator_handle;
+wurfl_important_header_enumerator_handle wurfl_get_important_header_enumerator(wurfl_handle hwurfl);
+void wurfl_important_header_enumerator_destroy(wurfl_important_header_enumerator_handle);
+const char *wurfl_important_header_enumerator_get_value(wurfl_important_header_enumerator_handle);
+int wurfl_important_header_enumerator_is_valid(wurfl_important_header_enumerator_handle);
+void wurfl_important_header_enumerator_move_next(wurfl_important_header_enumerator_handle);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _WURFL_H_
diff --git a/addons/wurfl/wurfl.c b/addons/wurfl/wurfl.c
new file mode 100644
index 0000000..4df6473
--- /dev/null
+++ b/addons/wurfl/wurfl.c
@@ -0,0 +1,779 @@
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <import/ebmbtree.h>
+#include <import/ebsttree.h>
+
+#include <haproxy/api.h>
+#include <haproxy/arg.h>
+#include <haproxy/buf-t.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/chunk.h>
+#include <haproxy/errors.h>
+#include <haproxy/global.h>
+#include <haproxy/http_ana.h>
+#include <haproxy/http_fetch.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/log.h>
+#include <haproxy/sample.h>
+#include <haproxy/tools.h>
+
+#include <wurfl/wurfl.h>
+
+static struct {
+ char *data_file; /* the WURFL data file */
+ char *cache_size; /* the WURFL cache parameters */
+ struct list patch_file_list; /* the list of WURFL patch file to use */
+ char information_list_separator; /* the separator used in request to separate values */
+ struct list information_list; /* the list of WURFL data to return into request */
+ void *handle; /* the handle to WURFL engine */
+ struct eb_root btree; /* btree containing info (name/type) on WURFL data to return */
+} global_wurfl = {
+ .data_file = NULL,
+ .cache_size = NULL,
+ .information_list_separator = ',',
+ .information_list = LIST_HEAD_INIT(global_wurfl.information_list),
+ .patch_file_list = LIST_HEAD_INIT(global_wurfl.patch_file_list),
+ .handle = NULL,
+};
+
+#ifdef WURFL_DEBUG
+inline static void ha_wurfl_log(char * message, ...)
+{
+ char logbuf[256];
+ va_list argp;
+
+ va_start(argp, message);
+ vsnprintf(logbuf, sizeof(logbuf), message, argp);
+ va_end(argp);
+ send_log(NULL, LOG_NOTICE, "%s", logbuf);
+}
+#else
+inline static void ha_wurfl_log(char * message, ...)
+{
+}
+#endif
+
+#define HA_WURFL_MAX_HEADER_LENGTH 1024
+
+typedef char *(*PROP_CALLBACK_FUNC)(wurfl_handle wHandle, wurfl_device_handle dHandle);
+
+enum wurfl_data_type {
+ HA_WURFL_DATA_TYPE_UNKNOWN = 0,
+ HA_WURFL_DATA_TYPE_CAP = 100,
+ HA_WURFL_DATA_TYPE_VCAP = 200,
+ HA_WURFL_DATA_TYPE_PROPERTY = 300
+};
+
+typedef struct {
+ char *name;
+ enum wurfl_data_type type;
+ PROP_CALLBACK_FUNC func_callback;
+ struct ebmb_node nd;
+} wurfl_data_t;
+
+static const char HA_WURFL_MODULE_VERSION[] = "2.0";
+static const char HA_WURFL_ISDEVROOT_FALSE[] = "FALSE";
+static const char HA_WURFL_ISDEVROOT_TRUE[] = "TRUE";
+
+static const char HA_WURFL_DATA_TYPE_UNKNOWN_STRING[] = "unknown";
+static const char HA_WURFL_DATA_TYPE_CAP_STRING[] = "capability";
+static const char HA_WURFL_DATA_TYPE_VCAP_STRING[] = "virtual_capability";
+static const char HA_WURFL_DATA_TYPE_PROPERTY_STRING[] = "property";
+
+static const char *ha_wurfl_retrieve_header(const char *header_name, const void *wh);
+static const char *ha_wurfl_get_wurfl_root_id (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_isdevroot (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_api_version (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_engine_target (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_info (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_last_load_time (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_normalized_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *ha_wurfl_get_wurfl_useragent_priority (wurfl_handle wHandle, wurfl_device_handle dHandle);
+static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle wHandle, wurfl_device_handle dHandle);
+
+// ordered property=>function map, suitable for binary search
+static const struct {
+ const char *name;
+ const char *(*func)(wurfl_handle wHandle, wurfl_device_handle dHandle);
+} wurfl_properties_function_map [] = {
+ {"wurfl_api_version", ha_wurfl_get_wurfl_api_version},
+ {"wurfl_engine_target", ha_wurfl_get_wurfl_engine_target}, // kept for backward conf file compat
+ {"wurfl_id", ha_wurfl_get_wurfl_id },
+ {"wurfl_info", ha_wurfl_get_wurfl_info },
+ {"wurfl_isdevroot", ha_wurfl_get_wurfl_isdevroot},
+ {"wurfl_last_load_time", ha_wurfl_get_wurfl_last_load_time},
+ {"wurfl_normalized_useragent", ha_wurfl_get_wurfl_normalized_useragent},
+ {"wurfl_root_id", ha_wurfl_get_wurfl_root_id},
+ {"wurfl_useragent", ha_wurfl_get_wurfl_useragent},
+ {"wurfl_useragent_priority", ha_wurfl_get_wurfl_useragent_priority }, // kept for backward conf file compat
+};
+static const int HA_WURFL_PROPERTIES_NBR = 10;
+
+typedef struct {
+ struct list list;
+ wurfl_data_t data;
+} wurfl_information_t;
+
+typedef struct {
+ struct list list;
+ char *patch_file_path;
+} wurfl_patches_t;
+
+typedef struct {
+ struct sample *wsmp;
+ char header_value[HA_WURFL_MAX_HEADER_LENGTH + 1];
+} ha_wurfl_header_t;
+
+/*
+ * configuration parameters parsing functions
+ */
+static int ha_wurfl_cfg_data_file(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+
+ if (*(args[1]) == 0) {
+ memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+ return -1;
+ }
+
+ global_wurfl.data_file = strdup(args[1]);
+ return 0;
+}
+
+static int ha_wurfl_cfg_cache(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+ return -1;
+ }
+
+ global_wurfl.cache_size = strdup(args[1]);
+ return 0;
+}
+
+static int ha_wurfl_cfg_engine_mode(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ // kept for backward conf file compat
+ return 0;
+}
+
+static int ha_wurfl_cfg_information_list_separator(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ if (*(args[1]) == 0) {
+ memprintf(err, "WURFL: %s expects a single character.\n", args[0]);
+ return -1;
+ }
+
+ if (strlen(args[1]) > 1) {
+ memprintf(err, "WURFL: %s expects a single character, got %s.\n", args[0], args[1]);
+ return -1;
+ }
+
+ global_wurfl.information_list_separator = *args[1];
+ return 0;
+}
+
+static int ha_wurfl_cfg_information_list(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int argIdx = 1;
+ wurfl_information_t *wi;
+
+ if (*(args[argIdx]) == 0) {
+ memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+ return -1;
+ }
+
+ while (*(args[argIdx])) {
+ wi = calloc(1, sizeof(*wi));
+
+ if (wi == NULL) {
+ memprintf(err, "WURFL: Error allocating memory for %s element.\n", args[0]);
+ return -1;
+ }
+
+ wi->data.name = strdup(args[argIdx]);
+ wi->data.type = HA_WURFL_DATA_TYPE_UNKNOWN;
+ wi->data.func_callback = NULL;
+ LIST_APPEND(&global_wurfl.information_list, &wi->list);
+ ++argIdx;
+ }
+
+ return 0;
+}
+
+static int ha_wurfl_cfg_patch_file_list(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ int argIdx = 1;
+ wurfl_patches_t *wp;
+
+ if (*(args[argIdx]) == 0) {
+ memprintf(err, "WURFL: %s expects a value.\n", args[0]);
+ return -1;
+ }
+
+ while (*(args[argIdx])) {
+ wp = calloc(1, sizeof(*wp));
+
+ if (wp == NULL) {
+ memprintf(err, "WURFL: Error allocating memory for %s element.\n", args[0]);
+ return -1;
+ }
+
+ wp->patch_file_path = strdup(args[argIdx]);
+ LIST_APPEND(&global_wurfl.patch_file_list, &wp->list);
+ ++argIdx;
+ }
+
+ return 0;
+}
+
+static int ha_wurfl_cfg_useragent_priority(char **args, int section_type, struct proxy *curpx,
+ const struct proxy *defpx, const char *file, int line,
+ char **err)
+{
+ // this feature is deprecated, keeping only not to break compatibility
+ // with old configuration files.
+ return 0;
+}
+
+/*
+ * module init / deinit functions. Returns 0 if OK, or a combination of ERR_*.
+ */
+
+static int ha_wurfl_init(void)
+{
+ wurfl_information_t *wi;
+ wurfl_patches_t *wp;
+ wurfl_data_t * wn;
+ int wurfl_result_code = WURFL_OK;
+ int len;
+
+ // wurfl-data-file not configured, WURFL is not used so don't try to
+ // configure it.
+ if (global_wurfl.data_file == NULL)
+ return ERR_NONE;
+
+ ha_notice("WURFL: Loading module v.%s\n", HA_WURFL_MODULE_VERSION);
+ // creating WURFL handler
+ global_wurfl.handle = wurfl_create();
+
+ if (global_wurfl.handle == NULL) {
+ ha_warning("WURFL: Engine handler creation failed\n");
+ return ERR_WARN;
+ }
+
+ ha_notice("WURFL: Engine handler created - API version %s\n", wurfl_get_api_version() );
+
+ // set wurfl data file
+ if (wurfl_set_root(global_wurfl.handle, global_wurfl.data_file) != WURFL_OK) {
+ ha_warning("WURFL: Engine setting root file failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+ return ERR_WARN;
+ }
+
+ ha_notice("WURFL: Engine root file set to %s\n", global_wurfl.data_file);
+ // just a log to inform which separator char has to be used
+ ha_notice("WURFL: Information list separator set to '%c'\n", global_wurfl.information_list_separator);
+
+ // load wurfl data needed ( and filter whose are supposed to be capabilities )
+ if (LIST_ISEMPTY(&global_wurfl.information_list)) {
+ ha_warning("WURFL: missing wurfl-information-list parameter in global configuration\n");
+ return ERR_WARN;
+ } else {
+ // ebtree initialization
+ global_wurfl.btree = EB_ROOT;
+
+ // checking if information is valid WURFL data ( cap, vcaps, properties )
+ list_for_each_entry(wi, &global_wurfl.information_list, list) {
+ // check if information is already loaded looking into btree
+ if (ebst_lookup(&global_wurfl.btree, wi->data.name) == NULL) {
+ if ((wi->data.func_callback = (PROP_CALLBACK_FUNC) ha_wurfl_get_property_callback(wi->data.name)) != NULL) {
+ wi->data.type = HA_WURFL_DATA_TYPE_PROPERTY;
+#ifdef WURFL_DEBUG
+ ha_notice("WURFL: [%s] is a valid wurfl data [property]\n",wi->data.name);
+#endif
+ } else if (wurfl_has_virtual_capability(global_wurfl.handle, wi->data.name)) {
+ wi->data.type = HA_WURFL_DATA_TYPE_VCAP;
+#ifdef WURFL_DEBUG
+ ha_notice("WURFL: [%s] is a valid wurfl data [virtual capability]\n",wi->data.name);
+#endif
+ } else {
+ // by default a cap type is assumed to be and we control it on engine load
+ wi->data.type = HA_WURFL_DATA_TYPE_CAP;
+
+ if (wurfl_add_requested_capability(global_wurfl.handle, wi->data.name) != WURFL_OK) {
+ ha_warning("WURFL: capability filtering failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+ return ERR_WARN;
+ }
+
+ ha_notice("WURFL: [%s] treated as wurfl capability. Will check its validity later, on engine load\n",wi->data.name);
+ }
+
+ // ebtree insert here
+ len = strlen(wi->data.name);
+
+ wn = malloc(sizeof(wurfl_data_t) + len + 1);
+
+ if (wn == NULL) {
+ ha_warning("WURFL: Error allocating memory for information tree element.\n");
+ return ERR_WARN;
+ }
+
+ wn->name = wi->data.name;
+ wn->type = wi->data.type;
+ wn->func_callback = wi->data.func_callback;
+ memcpy(wn->nd.key, wi->data.name, len);
+ wn->nd.key[len] = 0;
+
+ if (!ebst_insert(&global_wurfl.btree, &wn->nd)) {
+ ha_warning("WURFL: [%s] not inserted in btree\n",wn->name);
+ return ERR_WARN;
+ }
+
+ } else {
+#ifdef WURFL_DEBUG
+ ha_notice("WURFL: [%s] already loaded\n",wi->data.name);
+#endif
+ }
+
+ }
+
+ }
+
+
+ // adding WURFL patches if needed
+ if (!LIST_ISEMPTY(&global_wurfl.patch_file_list)) {
+
+ list_for_each_entry(wp, &global_wurfl.patch_file_list, list) {
+ if (wurfl_add_patch(global_wurfl.handle, wp->patch_file_path) != WURFL_OK) {
+ ha_warning("WURFL: Engine adding patch file failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+ return ERR_WARN;
+ }
+ ha_notice("WURFL: Engine patch file added %s\n", wp->patch_file_path);
+
+ }
+
+ }
+
+ // setting cache provider if specified in cfg, otherwise let engine choose
+ if (global_wurfl.cache_size != NULL) {
+ if (strpbrk(global_wurfl.cache_size, ",") != NULL) {
+ wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_DOUBLE_LRU, global_wurfl.cache_size) ;
+ } else {
+ if (strcmp(global_wurfl.cache_size, "0")) {
+ wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_LRU, global_wurfl.cache_size) ;
+ } else {
+ wurfl_result_code = wurfl_set_cache_provider(global_wurfl.handle, WURFL_CACHE_PROVIDER_NONE, 0);
+ }
+
+ }
+
+ if (wurfl_result_code != WURFL_OK) {
+ ha_warning("WURFL: Setting cache to [%s] failed - %s\n", global_wurfl.cache_size, wurfl_get_error_message(global_wurfl.handle));
+ return ERR_WARN;
+ }
+
+ ha_notice("WURFL: Cache set to [%s]\n", global_wurfl.cache_size);
+ }
+
+ // loading WURFL engine
+ if (wurfl_load(global_wurfl.handle) != WURFL_OK) {
+ ha_warning("WURFL: Engine load failed - %s\n", wurfl_get_error_message(global_wurfl.handle));
+ return ERR_WARN;
+ }
+
+ ha_notice("WURFL: Engine loaded\n");
+ ha_notice("WURFL: Module load completed\n");
+ return ERR_NONE;
+}
+
+static void ha_wurfl_deinit(void)
+{
+ wurfl_information_t *wi, *wi2;
+ wurfl_patches_t *wp, *wp2;
+
+ send_log(NULL, LOG_NOTICE, "WURFL: Unloading module v.%s\n", HA_WURFL_MODULE_VERSION);
+ wurfl_destroy(global_wurfl.handle);
+ global_wurfl.handle = NULL;
+ ha_free(&global_wurfl.data_file);
+ ha_free(&global_wurfl.cache_size);
+
+ list_for_each_entry_safe(wi, wi2, &global_wurfl.information_list, list) {
+ LIST_DELETE(&wi->list);
+ free(wi);
+ }
+
+ list_for_each_entry_safe(wp, wp2, &global_wurfl.patch_file_list, list) {
+ LIST_DELETE(&wp->list);
+ free(wp);
+ }
+
+ send_log(NULL, LOG_NOTICE, "WURFL: Module unloaded\n");
+}
+
+static int ha_wurfl_get_all(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+ wurfl_device_handle dHandle;
+ struct buffer *temp;
+ wurfl_information_t *wi;
+ ha_wurfl_header_t wh;
+ struct channel *chn;
+ struct htx *htx;
+
+ ha_wurfl_log("WURFL: starting ha_wurfl_get_all\n");
+
+ chn = (smp->strm ? &smp->strm->req : NULL);
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ if (!htx)
+ return 0;
+
+ wh.wsmp = smp;
+
+ dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
+
+ temp = get_trash_chunk();
+ chunk_reset(temp);
+
+ if (!dHandle) {
+ ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
+ goto wurfl_get_all_completed;
+ }
+
+ list_for_each_entry(wi, &global_wurfl.information_list, list) {
+
+ switch(wi->data.type) {
+ case HA_WURFL_DATA_TYPE_UNKNOWN :
+ ha_wurfl_log("WURFL: %s is of an %s type\n", wi->data.name, HA_WURFL_DATA_TYPE_UNKNOWN_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s", HA_WURFL_DATA_TYPE_UNKNOWN_STRING, wi->data.name);
+#endif
+ break;
+ case HA_WURFL_DATA_TYPE_CAP :
+ ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_CAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_CAP_STRING, wi->data.name);
+#endif
+ chunk_appendf(temp, "%s", wurfl_device_get_capability(dHandle, wi->data.name));
+ break;
+ case HA_WURFL_DATA_TYPE_VCAP :
+ ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_VCAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_VCAP_STRING, wi->data.name);
+#endif
+ chunk_appendf(temp, "%s", wurfl_device_get_virtual_capability(dHandle, wi->data.name));
+ break;
+ case HA_WURFL_DATA_TYPE_PROPERTY :
+ ha_wurfl_log("WURFL: %s is a %s\n", wi->data.name, HA_WURFL_DATA_TYPE_PROPERTY_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_PROPERTY_STRING, wi->data.name);
+#endif
+ chunk_appendf(temp, "%s", wi->data.func_callback(global_wurfl.handle, dHandle));
+ break;
+ }
+
+ // append wurfl-information-list-separator
+ chunk_appendf(temp, "%c", global_wurfl.information_list_separator);
+ }
+
+wurfl_get_all_completed:
+
+ wurfl_device_destroy(dHandle);
+ smp->data.u.str.area = temp->area;
+ smp->data.u.str.data = temp->data;
+
+ // remove trailing wurfl-information-list-separator
+ if (temp->data) {
+ temp->area[temp->data] = '\0';
+ --smp->data.u.str.data;
+ }
+
+ smp->data.type = SMP_T_STR;
+ return 1;
+}
+
+static int ha_wurfl_get(const struct arg *args, struct sample *smp, const char *kw, void *private)
+{
+ wurfl_device_handle dHandle;
+ struct buffer *temp;
+ wurfl_data_t *wn = NULL;
+ struct ebmb_node *node;
+ ha_wurfl_header_t wh;
+ int i = 0;
+ struct channel *chn;
+ struct htx *htx;
+
+ ha_wurfl_log("WURFL: starting ha_wurfl_get\n");
+
+ chn = (smp->strm ? &smp->strm->req : NULL);
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ if (!htx)
+ return 0;
+
+ wh.wsmp = smp;
+
+ dHandle = wurfl_lookup(global_wurfl.handle, &ha_wurfl_retrieve_header, &wh);
+
+ temp = get_trash_chunk();
+ chunk_reset(temp);
+
+ if (!dHandle) {
+ ha_wurfl_log("WURFL: unable to retrieve device from request %s\n", wurfl_get_error_message(global_wurfl.handle));
+ goto wurfl_get_completed;
+ }
+
+ while (args[i].data.str.area) {
+ node = ebst_lookup(&global_wurfl.btree, args[i].data.str.area);
+
+ if (node) {
+
+ wn = container_of(node, wurfl_data_t, nd);
+
+ switch(wn->type) {
+ case HA_WURFL_DATA_TYPE_UNKNOWN :
+ ha_wurfl_log("WURFL: %s is of an %s type\n", wn->name, HA_WURFL_DATA_TYPE_UNKNOWN_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s", HA_WURFL_DATA_TYPE_UNKNOWN_STRING, wn->name);
+#endif
+ break;
+ case HA_WURFL_DATA_TYPE_CAP :
+ ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_CAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_CAP_STRING, wn->name);
+#endif
+ chunk_appendf(temp, "%s", wurfl_device_get_capability(dHandle, wn->name));
+ break;
+ case HA_WURFL_DATA_TYPE_VCAP :
+ ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_VCAP_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_VCAP_STRING, wn->name);
+#endif
+ chunk_appendf(temp, "%s", wurfl_device_get_virtual_capability(dHandle, wn->name));
+ break;
+ case HA_WURFL_DATA_TYPE_PROPERTY :
+ ha_wurfl_log("WURFL: %s is a %s\n", wn->name, HA_WURFL_DATA_TYPE_PROPERTY_STRING);
+#ifdef WURFL_HEADER_WITH_DETAILS
+ // write WURFL property type and name before its value...
+ chunk_appendf(temp, "%s=%s=", HA_WURFL_DATA_TYPE_PROPERTY_STRING, wn->name);
+#endif
+ chunk_appendf(temp, "%s", wn->func_callback(global_wurfl.handle, dHandle));
+ break;
+ }
+
+ // append wurfl-information-list-separator
+ chunk_appendf(temp, "%c", global_wurfl.information_list_separator);
+
+ } else {
+ ha_wurfl_log("WURFL: %s not in wurfl-information-list \n",
+ args[i].data.str.area);
+ }
+
+ i++;
+ }
+
+wurfl_get_completed:
+
+ wurfl_device_destroy(dHandle);
+ smp->data.u.str.area = temp->area;
+ smp->data.u.str.data = temp->data;
+
+ // remove trailing wurfl-information-list-separator
+ if (temp->data) {
+ temp->area[temp->data] = '\0';
+ --smp->data.u.str.data;
+ }
+
+ smp->data.type = SMP_T_STR;
+ return 1;
+}
+
+static struct cfg_kw_list wurflcfg_kws = {{ }, {
+ { CFG_GLOBAL, "wurfl-data-file", ha_wurfl_cfg_data_file },
+ { CFG_GLOBAL, "wurfl-information-list-separator", ha_wurfl_cfg_information_list_separator },
+ { CFG_GLOBAL, "wurfl-information-list", ha_wurfl_cfg_information_list },
+ { CFG_GLOBAL, "wurfl-patch-file", ha_wurfl_cfg_patch_file_list },
+ { CFG_GLOBAL, "wurfl-cache-size", ha_wurfl_cfg_cache },
+ { CFG_GLOBAL, "wurfl-engine-mode", ha_wurfl_cfg_engine_mode },
+ { CFG_GLOBAL, "wurfl-useragent-priority", ha_wurfl_cfg_useragent_priority },
+ { 0, NULL, NULL },
+ }
+};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &wurflcfg_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list fetch_kws = {ILH, {
+ { "wurfl-get-all", ha_wurfl_get_all, 0, NULL, SMP_T_STR, SMP_USE_HRQHV },
+ { "wurfl-get", ha_wurfl_get, ARG12(1,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR,STR), NULL, SMP_T_STR, SMP_USE_HRQHV },
+ { NULL, NULL, 0, 0, 0 },
+ }
+};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &fetch_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_conv_kw_list conv_kws = {ILH, {
+ { NULL, NULL, 0, 0, 0 },
+ }
+};
+
+INITCALL1(STG_REGISTER, sample_register_convs, &conv_kws);
+
+// WURFL properties wrapper functions
+static const char *ha_wurfl_get_wurfl_root_id (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ if (wurfl_device_get_root_id(dHandle))
+ return wurfl_device_get_root_id(dHandle);
+ else
+ return "";
+}
+
+static const char *ha_wurfl_get_wurfl_id (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return wurfl_device_get_id(dHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_isdevroot (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ if (wurfl_device_is_actual_device_root(dHandle))
+ return HA_WURFL_ISDEVROOT_TRUE;
+ else
+ return HA_WURFL_ISDEVROOT_FALSE;
+}
+
+static const char *ha_wurfl_get_wurfl_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return wurfl_device_get_original_useragent(dHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_api_version (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return wurfl_get_api_version();
+}
+
+static const char *ha_wurfl_get_wurfl_engine_target (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return "default";
+}
+
+static const char *ha_wurfl_get_wurfl_info (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return wurfl_get_wurfl_info(wHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_last_load_time (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return wurfl_get_last_load_time_as_string(wHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_normalized_useragent (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return wurfl_device_get_normalized_useragent(dHandle);
+}
+
+static const char *ha_wurfl_get_wurfl_useragent_priority (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ return "default";
+}
+
+// call function for WURFL properties
+static const char *(*ha_wurfl_get_property_callback(char *name)) (wurfl_handle wHandle, wurfl_device_handle dHandle)
+{
+ int position;
+ int begin = 0;
+ int end = HA_WURFL_PROPERTIES_NBR - 1;
+ int cond = 0;
+
+ while(begin <= end) {
+ position = (begin + end) / 2;
+
+ if((cond = strcmp(wurfl_properties_function_map[position].name, name)) == 0) {
+ ha_wurfl_log("WURFL: ha_wurfl_get_property_callback match %s\n", wurfl_properties_function_map[position].name );
+ return wurfl_properties_function_map[position].func;
+ } else if(cond < 0)
+ begin = position + 1;
+ else
+ end = position - 1;
+
+ }
+
+ return NULL;
+}
+
+static const char *ha_wurfl_retrieve_header(const char *header_name, const void *wh)
+{
+ struct sample *smp;
+ struct channel *chn;
+ struct htx *htx;
+ struct http_hdr_ctx ctx;
+ struct ist name;
+ int header_len = HA_WURFL_MAX_HEADER_LENGTH;
+
+ smp = ((ha_wurfl_header_t *)wh)->wsmp;
+ chn = (smp->strm ? &smp->strm->req : NULL);
+
+ ha_wurfl_log("WURFL: retrieve header (HTX) request [%s]\n", header_name);
+
+ //the header is searched from the beginning
+ ctx.blk = NULL;
+
+ // We could skip this check since ha_wurfl_retrieve_header is called from inside
+ // ha_wurfl_get()/ha_wurfl_get_all() that already perform the same check
+ // We choose to keep it in case ha_wurfl_retrieve_header will be called directly
+ htx = smp_prefetch_htx(smp, chn, NULL, 1);
+ if (!htx) {
+ return NULL;
+ }
+
+ name = ist2((char *)header_name, strlen(header_name));
+
+ // If 4th param is set, it works on full-line headers in whose comma is not a delimiter but is
+ // part of the syntax
+ if (!http_find_header(htx, name, &ctx, 1)) {
+ return NULL;
+ }
+
+ if (header_len > ctx.value.len)
+ header_len = ctx.value.len;
+
+ strncpy(((ha_wurfl_header_t *)wh)->header_value, ctx.value.ptr, header_len);
+
+ ((ha_wurfl_header_t *)wh)->header_value[header_len] = '\0';
+
+ ha_wurfl_log("WURFL: retrieve header request returns [%s]\n", ((ha_wurfl_header_t *)wh)->header_value);
+ return ((ha_wurfl_header_t *)wh)->header_value;
+}
+
+static void ha_wurfl_register_build_options()
+{
+ const char *ver = wurfl_get_api_version();
+ char *ptr = NULL;
+
+ memprintf(&ptr, "Built with WURFL support (%sversion %s)",
+ strcmp(ver, "1.11.2.100") ? "" : "dummy library ",
+ ver);
+ hap_register_build_opts(ptr, 1);
+}
+
+REGISTER_POST_CHECK(ha_wurfl_init);
+REGISTER_POST_DEINIT(ha_wurfl_deinit);
+INITCALL0(STG_REGISTER, ha_wurfl_register_build_options);