From 2d56399b0c15e14405087a73330e9c8451318ba1 Mon Sep 17 00:00:00 2001 From: Miroslav Zagorac Date: Sun, 12 Apr 2026 08:41:03 +0200 Subject: [PATCH] MEDIUM: otel: added configuration parser and event model Added the full configuration parser that reads the OTel filter's external configuration file and the event model that maps filter events to HAProxy channel analyzers. The event model in event.h defines an X-macro table (FLT_OTEL_EVENT_DEFINES) that maps each filter event to its HAProxy channel analyzer bit, sample fetch direction, and event name. Events cover stream lifecycle (start, stop, backend-set, idle-timeout), client and server sessions, request analyzers (frontend and backend TCP and HTTP inspection, switching rules, sticking rules, RDP cookie), response analyzers (TCP inspection, HTTP response processing), and HTTP headers, end, and reply callbacks. The event names are partially compatible with the SPOE filter. The flt_otel_event_data[] table in event.c is generated from the same X-macro and provides per-event metadata at runtime. The parser in parser.c implements section parsers for the three OTel configuration blocks: otel-instrumentation (tracer identity, log server, config file path, groups, scopes, ACLs, rate-limit, options for disabled/hard-errors/nolognorm, and debug-level), otel-group (group identity and scope list), and otel-scope (scope identity, span definitions with optional root/parent modifiers, attributes, events, baggages, status codes, inject/extract context operations, finish lists, idle-timeout, ACLs, and otel-event binding with optional if/unless ACL conditions). Each section has a post-parse callback that validates the parsed state. The top-level flt_otel_parse_cfg() temporarily registers these section parsers, loads the external configuration file via parse_cfg(), and handles deferred resolution of sample fetch arguments by saving them in conf->smp_args for later resolution in flt_otel_check() when full frontend and backend capabilities are available. The main flt_otel_parse() entry point was extended to parse the filter ID and config file keywords, verify that insecure-fork-wanted is enabled, and wire the parsed configuration into the flt_conf structure. The utility layer gained flt_otel_strtod() and flt_otel_strtoll() for validated string-to-number conversion used by rate-limit and debug-level parsing. --- addons/otel/Makefile | 1 + addons/otel/include/event.h | 135 ++++ addons/otel/include/filter.h | 9 +- addons/otel/include/include.h | 1 + addons/otel/include/parser.h | 159 +++- addons/otel/include/util.h | 6 + addons/otel/src/event.c | 19 + addons/otel/src/parser.c | 1318 ++++++++++++++++++++++++++++++++- addons/otel/src/util.c | 90 +++ 9 files changed, 1733 insertions(+), 5 deletions(-) create mode 100644 addons/otel/include/event.h create mode 100644 addons/otel/src/event.c diff --git a/addons/otel/Makefile b/addons/otel/Makefile index feddc6596..53c17b2fe 100644 --- a/addons/otel/Makefile +++ b/addons/otel/Makefile @@ -52,6 +52,7 @@ endif OPTIONS_OBJS += \ addons/otel/src/conf.o \ + addons/otel/src/event.o \ addons/otel/src/filter.o \ addons/otel/src/parser.o \ addons/otel/src/util.o diff --git a/addons/otel/include/event.h b/addons/otel/include/event.h new file mode 100644 index 000000000..8553a4ec4 --- /dev/null +++ b/addons/otel/include/event.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef _OTEL_EVENT_H_ +#define _OTEL_EVENT_H_ + +/* + * This must be defined in order for macro FLT_OTEL_EVENT_DEFINES + * and structure flt_otel_event_data to have the correct contents. + */ +#define AN__NONE 0 +#define AN__STREAM_START 0 /* on-stream-start */ +#define AN__STREAM_STOP 0 /* on-stream-stop */ +#define AN__IDLE_TIMEOUT 0 /* on-idle-timeout */ +#define AN__BACKEND_SET 0 /* on-backend-set */ +#define AN_REQ_HTTP_HEADERS 0 /* on-http-headers-request */ +#define AN_RES_HTTP_HEADERS 0 /* on-http-headers-response */ +#define AN_REQ_HTTP_END 0 /* on-http-end-request */ +#define AN_RES_HTTP_END 0 /* on-http-end-response */ +#define AN_RES_HTTP_REPLY 0 /* on-http-reply */ +#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 +#define SMP_OPT_DIR_ 0xff + +/* + * 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_OTEL_EVENT_NONE is used as an index for 'otel-scope' sections that do not + * have an event defined. The 'otel-scope' sections thus defined can be used + * within the 'otel-group' section. + * + * A description of the macro arguments can be found in the structure + * flt_otel_event_data definition. + * + * The following table is derived from the definitions AN_REQ_* and AN_RES_* + * found in the HAProxy include file include/haproxy/channel-t.h. + */ +#define FLT_OTEL_EVENT_DEFINES \ + /* Stream lifecycle pseudo-events (an_bit = 0, not tied to a channel analyzer) */ \ + FLT_OTEL_EVENT_DEF( NONE, , , , 0, "") \ + FLT_OTEL_EVENT_DEF( STREAM_START, , , , 0, "on-stream-start") \ + FLT_OTEL_EVENT_DEF( STREAM_STOP, , , , 0, "on-stream-stop") \ + FLT_OTEL_EVENT_DEF( CLIENT_SESS_START, REQ, CON_ACC, , 1, "on-client-session-start") \ + FLT_OTEL_EVENT_DEF( IDLE_TIMEOUT, , , , 0, "on-idle-timeout") \ + FLT_OTEL_EVENT_DEF( BACKEND_SET, , , , 0, "on-backend-set") \ + \ + /* Request analyzers */ \ +/* FLT_OTEL_EVENT_DEF( FLT_START_FE, REQ, , , , "on-filter-start") */ \ + FLT_OTEL_EVENT_DEF( INSPECT_FE, REQ, REQ_CNT, , 1, "on-frontend-tcp-request") \ + FLT_OTEL_EVENT_DEF( WAIT_HTTP, REQ, , , 1, "on-http-wait-request") \ + FLT_OTEL_EVENT_DEF( HTTP_BODY, REQ, , , 1, "on-http-body-request") \ + FLT_OTEL_EVENT_DEF( HTTP_PROCESS_FE, REQ, HRQ_HDR, , 1, "on-frontend-http-request") \ + FLT_OTEL_EVENT_DEF( SWITCHING_RULES, REQ, , , 1, "on-switching-rules-request") \ +/* FLT_OTEL_EVENT_DEF( FLT_START_BE, REQ, , , , "") */ \ + FLT_OTEL_EVENT_DEF( INSPECT_BE, REQ, REQ_CNT, REQ_CNT, 1, "on-backend-tcp-request") \ + FLT_OTEL_EVENT_DEF( HTTP_PROCESS_BE, REQ, HRQ_HDR, HRQ_HDR, 1, "on-backend-http-request") \ +/* FLT_OTEL_EVENT_DEF( HTTP_TARPIT, REQ, , , 1, "on-http-tarpit-request") */ \ + FLT_OTEL_EVENT_DEF( SRV_RULES, REQ, , , 1, "on-process-server-rules-request") \ + FLT_OTEL_EVENT_DEF( HTTP_INNER, REQ, , , 1, "on-http-process-request") \ + FLT_OTEL_EVENT_DEF( PRST_RDP_COOKIE, REQ, , , 1, "on-tcp-rdp-cookie-request") \ + FLT_OTEL_EVENT_DEF( STICKING_RULES, REQ, , , 1, "on-process-sticking-rules-request") \ +/* FLT_OTEL_EVENT_DEF( FLT_HTTP_HDRS, REQ, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( HTTP_XFER_BODY, REQ, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( WAIT_CLI, REQ, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( FLT_XFER_DATA, REQ, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( FLT_END, REQ, , , , "") */ \ + FLT_OTEL_EVENT_DEF( HTTP_HEADERS, REQ, HRQ_HDR, HRQ_HDR, 1, "on-http-headers-request") \ + FLT_OTEL_EVENT_DEF( HTTP_END, REQ, , , 1, "on-http-end-request") \ + FLT_OTEL_EVENT_DEF( CLIENT_SESS_END, REQ, , , 0, "on-client-session-end") \ + FLT_OTEL_EVENT_DEF(SERVER_UNAVAILABLE, REQ, , , 0, "on-server-unavailable") \ + \ + /* Response analyzers */ \ + FLT_OTEL_EVENT_DEF( SERVER_SESS_START, RES, , SRV_CON, 0, "on-server-session-start") \ +/* FLT_OTEL_EVENT_DEF( FLT_START_FE, RES, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( FLT_START_BE, RES, , , , "") */ \ + FLT_OTEL_EVENT_DEF( INSPECT, RES, RES_CNT, RES_CNT, 0, "on-tcp-response") \ + FLT_OTEL_EVENT_DEF( WAIT_HTTP, RES, , , 1, "on-http-wait-response") \ + FLT_OTEL_EVENT_DEF( STORE_RULES, RES, , , 1, "on-process-store-rules-response") \ + FLT_OTEL_EVENT_DEF( HTTP_PROCESS_BE, RES, HRS_HDR, HRS_HDR, 1, "on-http-response") \ + FLT_OTEL_EVENT_DEF( HTTP_HEADERS, RES, HRS_HDR, HRS_HDR, 1, "on-http-headers-response") \ + FLT_OTEL_EVENT_DEF( HTTP_END, RES, , , 0, "on-http-end-response") \ + FLT_OTEL_EVENT_DEF( HTTP_REPLY, RES, , , 0, "on-http-reply") \ +/* FLT_OTEL_EVENT_DEF( HTTP_PROCESS_FE, RES, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( FLT_HTTP_HDRS, RES, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( HTTP_XFER_BODY, RES, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( WAIT_CLI, RES, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( FLT_XFER_DATA, RES, , , , "") */ \ +/* FLT_OTEL_EVENT_DEF( FLT_END, RES, , , , "") */ \ + FLT_OTEL_EVENT_DEF( SERVER_SESS_END, RES, , , 0, "on-server-session-end") + +enum FLT_OTEL_EVENT_enum { +#define FLT_OTEL_EVENT_DEF(a,b,c,d,e,f) FLT_OTEL_EVENT_##b##_##a, + FLT_OTEL_EVENT_DEFINES + FLT_OTEL_EVENT_MAX +#undef FLT_OTEL_EVENT_DEF +}; + +/* Per-event metadata mapping analyzer bits to filter event names. */ +struct flt_otel_event_data { + uint an_bit; /* Used channel analyser. */ + const char *an_name; /* Channel analyser name. */ + 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. */ +}; + + +/* Per-event metadata table indexed by FLT_OTEL_EVENT_* constants. */ +extern const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX]; + +#endif /* _OTEL_EVENT_H_ */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + * + * vi: noexpandtab shiftwidth=8 tabstop=8 + */ diff --git a/addons/otel/include/filter.h b/addons/otel/include/filter.h index 2dbca001c..713eea4d0 100644 --- a/addons/otel/include/filter.h +++ b/addons/otel/include/filter.h @@ -3,6 +3,12 @@ #ifndef _OTEL_FILTER_H_ #define _OTEL_FILTER_H_ +#define FLT_OTEL_FMT_NAME "'" FLT_OTEL_OPT_NAME "' : " +#define FLT_OTEL_FMT_TYPE "'filter' : " + +#define FLT_OTEL_CONDITION_IF "if" +#define FLT_OTEL_CONDITION_UNLESS "unless" + /* Return codes for OTel filter operations. */ enum FLT_OTEL_RET_enum { FLT_OTEL_RET_ERROR = -1, @@ -12,7 +18,8 @@ enum FLT_OTEL_RET_enum { }; -extern const char *otel_flt_id; +extern const char *otel_flt_id; +extern struct flt_ops flt_otel_ops; #endif /* _OTEL_FILTER_H_ */ diff --git a/addons/otel/include/include.h b/addons/otel/include/include.h index a3e6c500f..e57d305a9 100644 --- a/addons/otel/include/include.h +++ b/addons/otel/include/include.h @@ -27,6 +27,7 @@ #include "config.h" #include "define.h" +#include "event.h" #include "conf.h" #include "conf_funcs.h" #include "filter.h" diff --git a/addons/otel/include/parser.h b/addons/otel/include/parser.h index d0c42c2dd..7ef1954e3 100644 --- a/addons/otel/include/parser.h +++ b/addons/otel/include/parser.h @@ -3,8 +3,163 @@ #ifndef _OTEL_PARSER_H_ #define _OTEL_PARSER_H_ -#define FLT_OTEL_SCOPE "OTEL" -#define FLT_OTEL_OPT_NAME "opentelemetry" +#define FLT_OTEL_SCOPE "OTEL" + +/* + * filter FLT_OTEL_OPT_NAME FLT_OTEL_OPT_FILTER_ID FLT_OTEL_OPT_CONFIG + */ +#define FLT_OTEL_OPT_NAME "opentelemetry" +#define FLT_OTEL_OPT_FILTER_ID "id" +#define FLT_OTEL_OPT_FILTER_ID_DEFAULT "otel-filter" +#define FLT_OTEL_OPT_CONFIG "config" + +#define FLT_OTEL_PARSE_SECTION_INSTR_ID "otel-instrumentation" +#define FLT_OTEL_PARSE_SECTION_GROUP_ID "otel-group" +#define FLT_OTEL_PARSE_SECTION_SCOPE_ID "otel-scope" + +#define FLT_OTEL_PARSE_SPAN_ROOT "root" +#define FLT_OTEL_PARSE_SPAN_PARENT "parent" +#define FLT_OTEL_PARSE_CTX_AUTONAME "-" +#define FLT_OTEL_PARSE_CTX_IGNORE_NAME '-' +#define FLT_OTEL_PARSE_CTX_USE_HEADERS "use-headers" +#define FLT_OTEL_PARSE_OPTION_HARDERR "hard-errors" +#define FLT_OTEL_PARSE_OPTION_DISABLED "disabled" +#define FLT_OTEL_PARSE_OPTION_NOLOGNORM "dontlog-normal" + +/* + * A description of the macro arguments can be found in the structure + * flt_otel_parse_data definition + */ +#define FLT_OTEL_PARSE_INSTR_DEFINES \ + FLT_OTEL_PARSE_INSTR_DEF( ID, 0, CHAR, 2, 2, "otel-instrumentation", " ") \ + FLT_OTEL_PARSE_INSTR_DEF( ACL, 0, CHAR, 3, 0, "acl", " [flags] [operator] ...") \ + FLT_OTEL_PARSE_INSTR_DEF( LOG, 0, CHAR, 2, 0, "log", " { global | [len ] [format ] [ []] }") \ + FLT_OTEL_PARSE_INSTR_DEF( CONFIG, 0, NONE, 2, 2, "config", " ") \ + FLT_OTEL_PARSE_INSTR_DEF( GROUPS, 0, NONE, 2, 0, "groups", " ...") \ + FLT_OTEL_PARSE_INSTR_DEF( SCOPES, 0, NONE, 2, 0, "scopes", " ...") \ + FLT_OTEL_PARSE_INSTR_DEF( RATE_LIMIT, 0, NONE, 2, 2, "rate-limit", " ") \ + FLT_OTEL_PARSE_INSTR_DEF( OPTION, 0, NONE, 2, 2, "option", " { disabled | dontlog-normal | hard-errors }") \ + FLT_OTEL_PARSE_INSTR_DEF(DEBUG_LEVEL, 0, NONE, 2, 2, "debug-level", " ") + +#define FLT_OTEL_PARSE_GROUP_DEFINES \ + FLT_OTEL_PARSE_GROUP_DEF( ID, 0, CHAR, 2, 2, "otel-group", " ") \ + FLT_OTEL_PARSE_GROUP_DEF(SCOPES, 0, NONE, 2, 0, "scopes", " ...") + +#define FLT_OTEL_PARSE_SCOPE_INJECT_HELP " [use-headers]" +#define FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP " [use-headers]" + +/* + * The first argument of the FLT_OTEL_PARSE_SCOPE_STATUS_DEF() macro is defined + * as otelc_span_status_t in . + */ +#define FLT_OTEL_PARSE_SCOPE_STATUS_DEFINES \ + FLT_OTEL_PARSE_SCOPE_STATUS_DEF(IGNORE, "ignore") \ + FLT_OTEL_PARSE_SCOPE_STATUS_DEF( UNSET, "unset" ) \ + FLT_OTEL_PARSE_SCOPE_STATUS_DEF( OK, "ok" ) \ + FLT_OTEL_PARSE_SCOPE_STATUS_DEF( ERROR, "error" ) + +/* + * In case the possibility of working with OpenTelemetry context via HAProxy + * variables is not used, args_max member of the structure flt_otel_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_OTEL_PARSE_SCOPE_DEFINES \ + FLT_OTEL_PARSE_SCOPE_DEF( ID, 0, CHAR, 2, 2, "otel-scope", " ") \ + FLT_OTEL_PARSE_SCOPE_DEF( SPAN, 0, NONE, 2, 7, "span", " [] [root]") \ + FLT_OTEL_PARSE_SCOPE_DEF( ATTRIBUTE, 1, NONE, 3, 0, "attribute", " ...") \ + FLT_OTEL_PARSE_SCOPE_DEF( EVENT, 1, NONE, 4, 0, "event", " ...") \ + FLT_OTEL_PARSE_SCOPE_DEF( BAGGAGE, 1, VAR, 3, 0, "baggage", " ...") \ + FLT_OTEL_PARSE_SCOPE_DEF( INJECT, 1, CTX, 2, 4, "inject", FLT_OTEL_PARSE_SCOPE_INJECT_HELP) \ + FLT_OTEL_PARSE_SCOPE_DEF( EXTRACT, 0, CTX, 2, 3, "extract", FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP) \ + FLT_OTEL_PARSE_SCOPE_DEF( STATUS, 1, NONE, 2, 0, "status", " [ ...]") \ + FLT_OTEL_PARSE_SCOPE_DEF( FINISH, 0, NONE, 2, 0, "finish", " ...") \ + FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", "