diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:18:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:18:05 +0000 |
commit | b46aad6df449445a9fc4aa7b32bd40005438e3f7 (patch) | |
tree | 751aa858ca01f35de800164516b298887382919d /addons | |
parent | Initial commit. (diff) | |
download | haproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.tar.xz haproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.zip |
Adding upstream version 2.9.5.upstream/2.9.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
102 files changed, 19580 insertions, 0 deletions
diff --git a/addons/51degrees/51d.c b/addons/51degrees/51d.c new file mode 100644 index 0000000..a23b468 --- /dev/null +++ b/addons/51degrees/51d.c @@ -0,0 +1,1179 @@ +#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/htx.h> +#include <haproxy/sample.h> +#include <haproxy/thread.h> +#include <haproxy/tools.h> +#include <haproxy/xxhash.h> + +#ifdef USE_51DEGREES_V4 +#include <hash/hash.h> +#undef MAP_TYPE +#include <hash/fiftyone.h> +#else +#include <51Degrees.h> +#endif + +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 + +#ifdef FIFTYONE_DEGREES_HASH_INCLUDED +#define _51D_HEADERS_BUFFER_SIZE BUFSIZE + +static THREAD_LOCAL struct { + char **buf; + int max; + int count; +} _51d_headers; + +static THREAD_LOCAL fiftyoneDegreesResultsHash *_51d_results = NULL; +#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; +#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED) + 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 +#elif defined(FIFTYONE_DEGREES_HASH_INCLUDED) + fiftyoneDegreesResourceManager manager; + int use_perf_graph; + int use_pred_graph; + int drift; + int difference; + int allow_unmatched; +#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 +#ifdef FIFTYONE_DEGREES_HASH_INCLUDED + .manager = { }, + .use_perf_graph = -1, + .use_pred_graph = -1, + .drift = -1, + .difference = -1, + .allow_unmatched = -1, +#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 FIFTYONE_DEGREES_HASH_INCLUDED +static int _51d_use_perf_graph(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) + global_51degrees.use_perf_graph = 1; + else if (strcmp(args[1], "off") == 0) + global_51degrees.use_perf_graph = 0; + else { + memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); + return -1; + } + + return 0; +} + +static int _51d_use_pred_graph(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) + global_51degrees.use_pred_graph = 1; + else if (strcmp(args[1], "off") == 0) + global_51degrees.use_pred_graph = 0; + else { + memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); + return -1; + } + + return 0; +} + +static int _51d_drift(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.drift = atoi(args[1]); + if (global_51degrees.drift < 0) { + memprintf(err, "'%s' expects a positive numeric value, got '%s'.", + args[0], args[1]); + return -1; + } + + return 0; +} + +static int _51d_difference(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.difference = atoi(args[1]); + if (global_51degrees.difference < 0) { + memprintf(err, "'%s' expects a positive numeric value, got '%s'.", + args[0], args[1]); + return -1; + } + + return 0; +} + +static int _51d_allow_unmatched(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + if (too_many_args(1, args, err, NULL)) + return -1; + + if (strcmp(args[1], "on") == 0) + global_51degrees.allow_unmatched = 1; + else if (strcmp(args[1], "off") == 0) + global_51degrees.allow_unmatched = 0; + else { + memprintf(err, "'%s' expects either 'on' or 'off' but got '%s'.", args[0], args[1]); + return -1; + } + + return 0; +} + +static int _51d_init_internal() +{ + fiftyoneDegreesDataSetHash *ds; + int hdr_count; + int i, ret = 0; + + ds = (fiftyoneDegreesDataSetHash *)fiftyoneDegreesDataSetGet(&global_51degrees.manager); + + hdr_count = ds->b.b.uniqueHeaders->count; + if (hdr_count > _51d_headers.max) + hdr_count = _51d_headers.max; + + _51d_results = fiftyoneDegreesResultsHashCreate(&global_51degrees.manager, hdr_count, 0); + if (!_51d_results) + goto out; + + for (i = 0; i < hdr_count; i++) { + _51d_headers.buf[i] = malloc(_51D_HEADERS_BUFFER_SIZE); + if (!_51d_headers.buf[i]) + goto out; + _51d_headers.count++; + } + + /* success */ + ret = 1; + +out: + fiftyoneDegreesDataSetRelease((fiftyoneDegreesDataSetBase *)ds); + return ret; +} + +static fiftyoneDegreesEvidenceKeyValuePairArray * _51d_get_evidence(struct sample *smp) +{ + fiftyoneDegreesEvidenceKeyValuePairArray *evidence; + fiftyoneDegreesDataSetHash *ds; + size_t size; + struct channel *chn; + struct htx *htx; + struct http_hdr_ctx ctx; + struct ist name; + int i; + + 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); + + ds = (fiftyoneDegreesDataSetHash *)_51d_results->b.b.dataSet; + size = _51d_headers.count * 2; + + evidence = fiftyoneDegreesEvidenceCreate(size); + if (!evidence) + return NULL; + + for (i = 0; i < _51d_headers.count; i++) { + fiftyoneDegreesHeader *hdr = &ds->b.b.uniqueHeaders->items[i]; + name = ist2(hdr->name, hdr->nameLength); + ctx.blk = NULL; + + if (http_find_header(htx, name, &ctx, 1)) { + size_t len = ctx.value.len; + + if (unlikely(len >= _51D_HEADERS_BUFFER_SIZE)) + len = _51D_HEADERS_BUFFER_SIZE - 1; + + memcpy(_51d_headers.buf[i], ctx.value.ptr, len); + _51d_headers.buf[i][len] = '\0'; + + fiftyoneDegreesEvidenceAddString( + evidence, + FIFTYONE_DEGREES_EVIDENCE_HTTP_HEADER_STRING, + name.ptr, + _51d_headers.buf[i]); + } + } + + return evidence; +} +#endif + +#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED) +#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 + const char* property_name; + int j; + +#elif defined(FIFTYONE_DEGREES_HASH_INCLUDED) +static void _51d_process_match(const struct arg *args, struct sample *smp) +{ + char valuesBuffer[1024]; +#endif + + char no_data[] = "NoData"; /* response when no data could be found */ + struct buffer *temp = get_trash_chunk(); + int i = 0, found; + +#if defined(FIFTYONE_DEGREES_HASH_INCLUDED) + FIFTYONE_DEGREES_EXCEPTION_CREATE; +#endif + + /* 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 +#ifdef FIFTYONE_DEGREES_HASH_INCLUDED + FIFTYONE_DEGREES_EXCEPTION_CLEAR; + + found = fiftyoneDegreesResultsHashGetValuesString( + _51d_results, args[i].data.str.area, + valuesBuffer, 1024, "|", + exception); + + if (FIFTYONE_DEGREES_EXCEPTION_FAILED || found <= 0) + found = 0; + else + chunk_appendf(temp, "%s", valuesBuffer); +#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) +{ + struct channel *chn; + struct htx *htx; +#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 FIFTYONE_DEGREES_HASH_INCLUDED + fiftyoneDegreesEvidenceKeyValuePairArray *evidence = NULL; + FIFTYONE_DEGREES_EXCEPTION_CREATE; +#endif + + 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 + +#ifdef FIFTYONE_DEGREES_HASH_INCLUDED + evidence = _51d_get_evidence(smp); + if (!evidence) + return 0; + + fiftyoneDegreesResultsHashFromEvidence( + _51d_results, evidence, exception); + fiftyoneDegreesEvidenceFree(evidence); + + if (FIFTYONE_DEGREES_EXCEPTION_FAILED) + return 0; + + _51d_process_match(args, smp); +#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 FIFTYONE_DEGREES_HASH_INCLUDED + FIFTYONE_DEGREES_EXCEPTION_CREATE; +#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. */ +#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED) +#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 + +#elif defined(FIFTYONE_DEGREES_HASH_INCLUDED) + fiftyoneDegreesResultsHashFromUserAgent(_51d_results, smp->data.u.str.area, + smp->data.u.str.data, exception); + if (FIFTYONE_DEGREES_EXCEPTION_FAILED) + return 0; + + _51d_process_match(args, smp); +#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 _51d_property_names *name; + char **_51d_property_list = NULL; +#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED) + struct buffer *temp; + fiftyoneDegreesDataSetInitStatus _51d_dataset_status = DATA_SET_INIT_STATUS_NOT_SET; +#elif defined(FIFTYONE_DEGREES_HASH_INCLUDED) + fiftyoneDegreesConfigHash config = fiftyoneDegreesHashInMemoryConfig; + fiftyoneDegreesPropertiesRequired properties = fiftyoneDegreesPropertiesDefault; + fiftyoneDegreesMemoryReader reader; + fiftyoneDegreesStatusCode status; + FIFTYONE_DEGREES_EXCEPTION_CREATE; +#endif + + 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; + } + +#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED) + _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 + +#elif defined(FIFTYONE_DEGREES_HASH_INCLUDED) + config.b.b.freeData = true; + + if (global_51degrees.use_perf_graph != -1) + config.usePerformanceGraph = global_51degrees.use_perf_graph; + if (global_51degrees.use_pred_graph != -1) + config.usePredictiveGraph = global_51degrees.use_pred_graph; + + if (global_51degrees.drift > 0) + config.drift = global_51degrees.drift; + if (global_51degrees.difference > 0) + config.difference = global_51degrees.difference; + + if (global_51degrees.allow_unmatched != -1) + config.b.allowUnmatched = global_51degrees.allow_unmatched; + + config.strings.concurrency = + config.properties.concurrency = + config.values.concurrency = + config.profiles.concurrency = + config.nodes.concurrency = + config.profileOffsets.concurrency = + config.maps.concurrency = + config.components.concurrency = + config.rootNodes.concurrency = global.nbthread; + + properties.array = (const char **)_51d_property_list; + properties.count = i; + + status = fiftyoneDegreesFileReadToByteArray(global_51degrees.data_file_path, &reader); + if (status == FIFTYONE_DEGREES_STATUS_SUCCESS && !FIFTYONE_DEGREES_EXCEPTION_FAILED) { + FIFTYONE_DEGREES_EXCEPTION_CLEAR; + + status = fiftyoneDegreesHashInitManagerFromMemory( + &global_51degrees.manager, + &config, + &properties, + reader.startByte, + reader.length, + exception); + } + + free(_51d_property_list); + _51d_property_list = NULL; + i = 0; + + if (status != FIFTYONE_DEGREES_STATUS_SUCCESS || FIFTYONE_DEGREES_EXCEPTION_FAILED) { + const char *message = fiftyoneDegreesStatusGetMessage(status, global_51degrees.data_file_path); + if (message) + ha_alert("51Degrees Setup - Error reading 51Degrees data file. %s\n", + message); + else + ha_alert("51Degrees Setup - Error reading 51Degrees data file.\n"); + return ERR_ALERT | ERR_FATAL; + } +#endif + + return ERR_NONE; +} + +static void deinit_51degrees(void) +{ + struct _51d_property_names *_51d_prop_name, *_51d_prop_nameb; + +#if defined(FIFTYONEDEGREES_H_PATTERN_INCLUDED) || defined(FIFTYONEDEGREES_H_TRIE_INCLUDED) + 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); +#endif + + 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 +} + +#ifdef FIFTYONE_DEGREES_HASH_INCLUDED +static int init_51degrees_per_thread() +{ + if (!global_51degrees.data_file_path) { + /* noop */ + return 1; + } + + _51d_headers.max = global.tune.max_http_hdr; + _51d_headers.buf = calloc(_51d_headers.max, sizeof(*_51d_headers.buf)); + _51d_headers.count = 0; + + if (!_51d_headers.buf) + return 0; + + if (!_51d_init_internal()) + return 0; + + return 1; +} + +static void deinit_51degrees_per_thread() +{ + int i; + + if (_51d_results) { + fiftyoneDegreesResultsHashFree(_51d_results); + _51d_results = NULL; + } + + if (_51d_headers.buf) { + for (i = 0; i < _51d_headers.max; i++) + free(_51d_headers.buf[i]); + free(_51d_headers.buf); + _51d_headers.buf = NULL; + } + + _51d_headers.max = 0; + _51d_headers.count = 0; +} +#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 }, +#ifdef FIFTYONE_DEGREES_HASH_INCLUDED + { CFG_GLOBAL, "51degrees-use-performance-graph", _51d_use_perf_graph }, + { CFG_GLOBAL, "51degrees-use-predictive-graph", _51d_use_pred_graph }, + { CFG_GLOBAL, "51degrees-drift", _51d_drift }, + { CFG_GLOBAL, "51degrees-difference", _51d_difference }, + { CFG_GLOBAL, "51degrees-allow-unmatched", _51d_allow_unmatched }, +#endif + { 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 +#elif defined(FIFTYONE_DEGREES_HASH_INCLUDED) + REGISTER_PER_THREAD_INIT(init_51degrees_per_thread); + REGISTER_PER_THREAD_DEINIT(deinit_51degrees_per_thread); +#ifndef FIFTYONEDEGREES_DUMMY_LIB + REGISTER_BUILD_OPTS("Built with 51Degrees V4 Hash support."); +#else + REGISTER_BUILD_OPTS("Built with 51Degrees V4 Hash 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/51degrees/dummy/v4hash/hash/fiftyone.h b/addons/51degrees/dummy/v4hash/hash/fiftyone.h new file mode 100644 index 0000000..fe9da87 --- /dev/null +++ b/addons/51degrees/dummy/v4hash/hash/fiftyone.h @@ -0,0 +1,34 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2022 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +/* ********************************************************************* + * Dummy library for HAProxy. This does not function, and is designed + * solely for HAProxy testing purposes. + * *********************************************************************/ +#ifndef FIFTYONE_DEGREES_SYNONYM_HASH_INCLUDED +#define FIFTYONE_DEGREES_SYNONYM_HASH_INCLUDED + +#ifndef FIFTYONEDEGREES_DUMMY_LIB +#define FIFTYONEDEGREES_DUMMY_LIB +#endif + +#endif diff --git a/addons/51degrees/dummy/v4hash/hash/hash.c b/addons/51degrees/dummy/v4hash/hash/hash.c new file mode 100644 index 0000000..e9a739e --- /dev/null +++ b/addons/51degrees/dummy/v4hash/hash/hash.c @@ -0,0 +1,130 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2022 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is the subject of the following patents and patent + * applications, owned by 51 Degrees Mobile Experts Limited of 5 Charlotte + * Close, Caversham, Reading, Berkshire, United Kingdom RG4 7BY: + * European Patent No. 3438848; and + * United States Patent No. 10,482,175. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +/* ********************************************************************* + * Dummy library for HAProxy. This does not function, and is designed + * solely for HAProxy testing purposes. + * *********************************************************************/ +#include "hash.h" +#include "fiftyone.h" + +static fiftyoneDegreesHeaders dummyHeaders = { }; +static fiftyoneDegreesDataSetBase dummyDataSet = { &dummyHeaders }; + +fiftyoneDegreesConfigHash fiftyoneDegreesHashInMemoryConfig; +fiftyoneDegreesPropertiesRequired fiftyoneDegreesPropertiesDefault; + +fiftyoneDegreesDataSetBase* fiftyoneDegreesDataSetGet( + fiftyoneDegreesResourceManager *manager) { + return &dummyDataSet; +} + +void fiftyoneDegreesResultsHashFree( + fiftyoneDegreesResultsHash* results) { + return; +} + +static fiftyoneDegreesResultsHash dummyResults = { }; + +fiftyoneDegreesResultsHash* fiftyoneDegreesResultsHashCreate( + fiftyoneDegreesResourceManager *manager, + uint32_t userAgentCapacity, + uint32_t overridesCapacity) { + return &dummyResults; +} + +void fiftyoneDegreesDataSetRelease(fiftyoneDegreesDataSetBase *dataSet) { + return; +} + +static fiftyoneDegreesEvidenceKeyValuePairArray dummyEvidence = { }; + +fiftyoneDegreesEvidenceKeyValuePairArray* +fiftyoneDegreesEvidenceCreate(uint32_t capacity) { + return &dummyEvidence; +} + +fiftyoneDegreesEvidenceKeyValuePair* fiftyoneDegreesEvidenceAddString( + fiftyoneDegreesEvidenceKeyValuePairArray *evidence, + fiftyoneDegreesEvidencePrefix prefix, + const char *field, + const char *originalValue) { + return NULL; +} + +size_t fiftyoneDegreesResultsHashGetValuesString( + fiftyoneDegreesResultsHash* results, + const char *propertyName, + char *buffer, + size_t bufferLength, + const char *separator, + fiftyoneDegreesException *exception) { + return 0; +} + +void fiftyoneDegreesResultsHashFromEvidence( + fiftyoneDegreesResultsHash *results, + fiftyoneDegreesEvidenceKeyValuePairArray *evidence, + fiftyoneDegreesException *exception) { + return; +} + +void fiftyoneDegreesEvidenceFree(fiftyoneDegreesEvidenceKeyValuePairArray *evidence) { + return; +} + +void fiftyoneDegreesResultsHashFromUserAgent( + fiftyoneDegreesResultsHash *results, + const char* userAgent, + size_t userAgentLength, + fiftyoneDegreesException *exception) { + return; +} + +fiftyoneDegreesStatusCode fiftyoneDegreesFileReadToByteArray( + const char *fileName, + fiftyoneDegreesMemoryReader *reader) { + return FIFTYONE_DEGREES_STATUS_SUCCESS; +} + +fiftyoneDegreesStatusCode +fiftyoneDegreesHashInitManagerFromMemory( + fiftyoneDegreesResourceManager *manager, + fiftyoneDegreesConfigHash *config, + fiftyoneDegreesPropertiesRequired *properties, + void *memory, + long size, + fiftyoneDegreesException *exception) { + return FIFTYONE_DEGREES_STATUS_SUCCESS; +} + +const char* fiftyoneDegreesStatusGetMessage( + fiftyoneDegreesStatusCode status, + const char *fileName) { + return NULL; +} diff --git a/addons/51degrees/dummy/v4hash/hash/hash.h b/addons/51degrees/dummy/v4hash/hash/hash.h new file mode 100644 index 0000000..5d04d17 --- /dev/null +++ b/addons/51degrees/dummy/v4hash/hash/hash.h @@ -0,0 +1,277 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2022 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is the subject of the following patents and patent + * applications, owned by 51 Degrees Mobile Experts Limited of 5 Charlotte + * Close, Caversham, Reading, Berkshire, United Kingdom RG4 7BY: + * European Patent No. 3438848; and + * United States Patent No. 10,482,175. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +/* ********************************************************************* + * Dummy library for HAProxy. This does not function, and is designed + * solely for HAProxy testing purposes. + * *********************************************************************/ +#ifndef FIFTYONE_DEGREES_HASH_INCLUDED +#define FIFTYONE_DEGREES_HASH_INCLUDED + +#ifndef FIFTYONEDEGREES_DUMMY_LIB +#define FIFTYONEDEGREES_DUMMY_LIB +#endif + +#include <stdlib.h> +#include <inttypes.h> + +typedef int bool; +enum { false, true }; + +typedef unsigned char byte; + +typedef enum e_fiftyone_degrees_status_code { + FIFTYONE_DEGREES_STATUS_SUCCESS, + FIFTYONE_DEGREES_STATUS_NOT_SET, +} fiftyoneDegreesStatusCode; + +typedef struct fiftyone_degrees_exception_t { + unsigned int status; +} fiftyoneDegreesException; + +#define FIFTYONE_DEGREES_EXCEPTION_CLEAR \ + exception->status = FIFTYONE_DEGREES_STATUS_NOT_SET; + +#define FIFTYONE_DEGREES_EXCEPTION_OKAY \ + (exception == NULL || exception->status == FIFTYONE_DEGREES_STATUS_NOT_SET) + +#define FIFTYONE_DEGREES_EXCEPTION_FAILED \ + (!FIFTYONE_DEGREES_EXCEPTION_OKAY) + +#define FIFTYONE_DEGREES_EXCEPTION_CREATE \ + fiftyoneDegreesException exceptionValue; \ + fiftyoneDegreesException *exception = &exceptionValue; \ + FIFTYONE_DEGREES_EXCEPTION_CLEAR + +#define FIFTYONE_DEGREES_ARRAY_TYPE(t, m) \ +typedef struct fiftyone_degrees_array_##t##_t { \ + uint32_t count; \ + uint32_t capacity; \ + t *items; \ + m \ +} t##Array; + +typedef struct fiftyone_degrees_results_base_t { + void *dataSet; +} fiftyoneDegreesResultsBase; + +typedef struct fiftyone_degrees_results_device_detection_t { + fiftyoneDegreesResultsBase b; +} fiftyoneDegreesResultsDeviceDetection; + +typedef struct fiftyone_degrees_collection_item_t { + +} fiftyoneDegreesCollectionItem; + +typedef struct fiftyone_degrees_list_t { + +} fiftyoneDegreesList; + +typedef struct fiftyone_degrees_evidence_key_value_pair_t { + +} fiftyoneDegreesEvidenceKeyValuePair; + +#define EVIDENCE_KEY_VALUE_MEMBERS \ + struct fiftyone_degrees_array_fiftyoneDegreesEvidenceKeyValuePair_t* pseudoEvidence; + +FIFTYONE_DEGREES_ARRAY_TYPE( + fiftyoneDegreesEvidenceKeyValuePair, + EVIDENCE_KEY_VALUE_MEMBERS) + +#define FIFTYONE_DEGREES_RESULTS_HASH_MEMBERS \ + fiftyoneDegreesResultsDeviceDetection b; \ + fiftyoneDegreesCollectionItem propertyItem; \ + fiftyoneDegreesList values; \ + fiftyoneDegreesEvidenceKeyValuePairArray* pseudoEvidence; + +typedef struct fiftyone_degrees_result_hash_t { + +} fiftyoneDegreesResultHash; + +FIFTYONE_DEGREES_ARRAY_TYPE( + fiftyoneDegreesResultHash, + FIFTYONE_DEGREES_RESULTS_HASH_MEMBERS) + +typedef fiftyoneDegreesResultHashArray fiftyoneDegreesResultsHash; + +typedef struct fiftyone_degrees_resource_manager_t { + +} fiftyoneDegreesResourceManager; + +typedef struct fiftyone_degrees_header_t { + const char* name; + size_t nameLength; +} fiftyoneDegreesHeader; + +#define FIFTYONE_DEGREES_HEADERS_MEMBERS \ + bool expectUpperPrefixedHeaders; \ + uint32_t pseudoHeadersCount; + +FIFTYONE_DEGREES_ARRAY_TYPE( + fiftyoneDegreesHeader, + FIFTYONE_DEGREES_HEADERS_MEMBERS); + +typedef fiftyoneDegreesHeaderArray fiftyoneDegreesHeaders; + +typedef struct fiftyone_degrees_dataset_base_t { + fiftyoneDegreesHeaders *uniqueHeaders; +} fiftyoneDegreesDataSetBase; + +typedef struct fiftyone_degrees_dataset_device_detection_t { + fiftyoneDegreesDataSetBase b; +} fiftyoneDegreesDataSetDeviceDetection; + +typedef struct fiftyone_degrees_dataset_hash_t { + fiftyoneDegreesDataSetDeviceDetection b; +} fiftyoneDegreesDataSetHash; + +typedef enum e_fiftyone_degrees_evidence_prefix { + FIFTYONE_DEGREES_EVIDENCE_HTTP_HEADER_STRING = 1 << 0, + FIFTYONE_DEGREES_EVIDENCE_HTTP_HEADER_IP_ADDRESSES = 1 << 1, + FIFTYONE_DEGREES_EVIDENCE_SERVER = 1 << 2, + FIFTYONE_DEGREES_EVIDENCE_QUERY = 1 << 3, + FIFTYONE_DEGREES_EVIDENCE_COOKIE = 1 << 4, + FIFTYONE_DEGREES_EVIDENCE_IGNORE = 1 << 7, +} fiftyoneDegreesEvidencePrefix; + +typedef struct fiftyone_degrees_config_base_t { + bool freeData; +} fiftyoneDegreesConfigBase; + +typedef struct fiftyone_degrees_config_device_detecton_t { + fiftyoneDegreesConfigBase b; + bool allowUnmatched; +} fiftyoneDegreesConfigDeviceDetection; + +typedef struct fiftyone_degrees_collection_config_t { + uint16_t concurrency; +} fiftyoneDegreesCollectionConfig; + +typedef struct fiftyone_degrees_config_hash_t { + fiftyoneDegreesConfigDeviceDetection b; + fiftyoneDegreesCollectionConfig strings; + fiftyoneDegreesCollectionConfig components; + fiftyoneDegreesCollectionConfig maps; + fiftyoneDegreesCollectionConfig properties; + fiftyoneDegreesCollectionConfig values; + fiftyoneDegreesCollectionConfig profiles; + fiftyoneDegreesCollectionConfig rootNodes; + fiftyoneDegreesCollectionConfig nodes; + fiftyoneDegreesCollectionConfig profileOffsets; + int32_t difference; + int32_t drift; + bool usePerformanceGraph; + bool usePredictiveGraph; +} fiftyoneDegreesConfigHash; + +extern fiftyoneDegreesConfigHash fiftyoneDegreesHashInMemoryConfig; + +typedef struct fiftyone_degrees_property_available_t { + +} fiftyoneDegreesPropertyAvailable; + +FIFTYONE_DEGREES_ARRAY_TYPE(fiftyoneDegreesPropertyAvailable,) + +typedef fiftyoneDegreesPropertyAvailableArray fiftyoneDegreesPropertiesAvailable; + +typedef struct fiftyone_degrees_properties_required_t { + const char **array; + int count; + const char *string; + fiftyoneDegreesPropertiesAvailable *existing; +} fiftyoneDegreesPropertiesRequired; + +extern fiftyoneDegreesPropertiesRequired fiftyoneDegreesPropertiesDefault; + +typedef struct fiftyone_degrees_memory_reader_t { + byte *startByte; + byte *current; + byte *lastByte; + long length; +} fiftyoneDegreesMemoryReader; + +fiftyoneDegreesDataSetBase* fiftyoneDegreesDataSetGet( + fiftyoneDegreesResourceManager *manager); + +void fiftyoneDegreesResultsHashFree( + fiftyoneDegreesResultsHash* results); + +fiftyoneDegreesResultsHash* fiftyoneDegreesResultsHashCreate( + fiftyoneDegreesResourceManager *manager, + uint32_t userAgentCapacity, + uint32_t overridesCapacity); + +void fiftyoneDegreesDataSetRelease(fiftyoneDegreesDataSetBase *dataSet); + +fiftyoneDegreesEvidenceKeyValuePairArray* fiftyoneDegreesEvidenceCreate(uint32_t capacity); + +fiftyoneDegreesEvidenceKeyValuePair* fiftyoneDegreesEvidenceAddString( + fiftyoneDegreesEvidenceKeyValuePairArray *evidence, + fiftyoneDegreesEvidencePrefix prefix, + const char *field, + const char *originalValue); + +size_t fiftyoneDegreesResultsHashGetValuesString( + fiftyoneDegreesResultsHash* results, + const char *propertyName, + char *buffer, + size_t bufferLength, + const char *separator, + fiftyoneDegreesException *exception); + +void fiftyoneDegreesResultsHashFromEvidence( + fiftyoneDegreesResultsHash *results, + fiftyoneDegreesEvidenceKeyValuePairArray *evidence, + fiftyoneDegreesException *exception); + +void fiftyoneDegreesEvidenceFree(fiftyoneDegreesEvidenceKeyValuePairArray *evidence); + +void fiftyoneDegreesResultsHashFromUserAgent( + fiftyoneDegreesResultsHash *results, + const char* userAgent, + size_t userAgentLength, + fiftyoneDegreesException *exception); + +fiftyoneDegreesStatusCode fiftyoneDegreesFileReadToByteArray( + const char *fileName, + fiftyoneDegreesMemoryReader *reader); + +fiftyoneDegreesStatusCode +fiftyoneDegreesHashInitManagerFromMemory( + fiftyoneDegreesResourceManager *manager, + fiftyoneDegreesConfigHash *config, + fiftyoneDegreesPropertiesRequired *properties, + void *memory, + long size, + fiftyoneDegreesException *exception); + +const char* fiftyoneDegreesStatusGetMessage( + fiftyoneDegreesStatusCode status, + const char *fileName); + +#endif 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 = ∝ + 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..5bf8d9e --- /dev/null +++ b/addons/ot/Makefile @@ -0,0 +1,73 @@ +# 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 + +OT_CFLAGS := $(OT_CFLAGS) -Iaddons/ot/include $(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..c9c4863 --- /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.loggers)), (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..c749960 --- /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, date.tv_sec, date.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), &date) / 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..0080dbd --- /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_DYNERR); + + 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_DYNERR); + + 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_DYNERR); + + 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_DYNERR); + + 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_DYNERR); + + 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_DYNERR); + + 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..c6375a6 --- /dev/null +++ b/addons/ot/src/conf.c @@ -0,0 +1,764 @@ +/*** + * 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); + } + free_acl_cond((*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 logger *logger, *loggerback; + + 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.loggers list %s", flt_ot_list_debug(&((*ptr)->proxy_log.loggers))); + list_for_each_entry_safe(logger, loggerback, &((*ptr)->proxy_log.loggers), list) { + LIST_DELETE(&(logger->list)); + FLT_OT_FREE(logger); + } + 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..f4f3e04 --- /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_logger(args, &(flt_ot_current_tracer->proxy_log.loggers), 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), &date, 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..fd04016 --- /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; + memcpy(value, data->u.str.area, retval); + value[retval] = '\0'; + } + 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; + memcpy(value, data->u.meth.str.area, retval); + value[retval] = '\0'; + } + } + 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..6885d20 --- /dev/null +++ b/addons/promex/service-prometheus.c @@ -0,0 +1,1655 @@ +/* + * 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) { + if ((sv->check.state & (CHK_ST_ENABLED|CHK_ST_PAUSED)) == CHK_ST_ENABLED) { + 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; + __fallthrough; + + 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; + __fallthrough; + + 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; + __fallthrough; + + 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; + __fallthrough; + + 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; + __fallthrough; + + 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; + __fallthrough; + + 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; + __fallthrough; + + case PROMEX_DUMPER_DONE: + default: + break; + } + + return 1; + + full: + sc_need_room(sc, channel_htx_recv_max(sc_ic(appctx_sc(appctx)), htx) + 1); + 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); + 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, 0); + 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(se_fl_test(appctx->sedesc, (SE_FL_EOS|SE_FL_ERROR|SE_FL_SHR|SE_FL_SHW)))) + goto out; + + /* Check if the input buffer is available. */ + if (!b_size(&res->buf)) { + sc_need_room(sc, 0); + 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; + __fallthrough; + + 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); + __fallthrough; + + 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; + __fallthrough; + + 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, sizeof(struct htx_blk) + 1); + goto out; + } + channel_add_input(res, 1); + } + res_htx->flags |= HTX_FL_EOM; + se_fl_set(appctx->sedesc, SE_FL_EOI); + appctx->st0 = PROMEX_ST_END; + __fallthrough; + + case PROMEX_ST_END: + se_fl_set(appctx->sedesc, SE_FL_EOS); + } + + 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: + se_fl_set(appctx->sedesc, SE_FL_ERROR); + 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); |