]> git.kaiwu.me - haproxy.git/commitdiff
MEDIUM: otel: added configuration parser and event model
authorMiroslav Zagorac <mzagorac@haproxy.com>
Sun, 12 Apr 2026 06:41:03 +0000 (08:41 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Mon, 13 Apr 2026 07:23:26 +0000 (09:23 +0200)
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
addons/otel/include/event.h [new file with mode: 0644]
addons/otel/include/filter.h
addons/otel/include/include.h
addons/otel/include/parser.h
addons/otel/include/util.h
addons/otel/src/event.c [new file with mode: 0644]
addons/otel/src/parser.c
addons/otel/src/util.c

index feddc6596ed3125f5f3a8742f6508434d3d4223a..53c17b2fe57ccb5c023329ca70527c07b87c0303 100644 (file)
@@ -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 (file)
index 0000000..8553a4e
--- /dev/null
@@ -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
+ */
index 2dbca001c4aef93fc7dcc2deddbe839a11b03a45..713eea4d08c41fffcc87834e7635e0c0b03c621e 100644 (file)
@@ -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_ */
 
index a3e6c500f14826f614f00e047a7067b847ee9722..e57d305a930050ad3cbc928750fe68e6c135cd20 100644 (file)
@@ -27,6 +27,7 @@
 
 #include "config.h"
 #include "define.h"
+#include "event.h"
 #include "conf.h"
 #include "conf_funcs.h"
 #include "filter.h"
index d0c42c2dd74639a447ffc40eeb13c16b4d3bf7c3..7ef1954e330544471e3178bd39f49b4664b19b68 100644 (file)
@@ -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_FILTER_ID_DEFAULT> FLT_OTEL_OPT_CONFIG <file>
+ */
+#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", " <name>")                                                                           \
+       FLT_OTEL_PARSE_INSTR_DEF(        ACL, 0, CHAR, 3, 0, "acl",                  " <name> <criterion> [flags] [operator] <value> ...")                                \
+       FLT_OTEL_PARSE_INSTR_DEF(        LOG, 0, CHAR, 2, 0, "log",                  " { global | <addr> [len <len>] [format <fmt>] <facility> [<level> [<minlevel>]] }") \
+       FLT_OTEL_PARSE_INSTR_DEF(     CONFIG, 0, NONE, 2, 2, "config",               " <file>")                                                                           \
+       FLT_OTEL_PARSE_INSTR_DEF(     GROUPS, 0, NONE, 2, 0, "groups",               " <name> ...")                                                                       \
+       FLT_OTEL_PARSE_INSTR_DEF(     SCOPES, 0, NONE, 2, 0, "scopes",               " <name> ...")                                                                       \
+       FLT_OTEL_PARSE_INSTR_DEF( RATE_LIMIT, 0, NONE, 2, 2, "rate-limit",           " <value>")                                                                          \
+       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",          " <value>")
+
+#define FLT_OTEL_PARSE_GROUP_DEFINES                                             \
+       FLT_OTEL_PARSE_GROUP_DEF(    ID, 0, CHAR, 2, 2, "otel-group", " <name>") \
+       FLT_OTEL_PARSE_GROUP_DEF(SCOPES, 0, NONE, 2, 0, "scopes",   " <name> ...")
+
+#define FLT_OTEL_PARSE_SCOPE_INJECT_HELP      " <name-prefix> [use-headers]"
+#define FLT_OTEL_PARSE_SCOPE_EXTRACT_HELP     " <name-prefix> [use-headers]"
+
+/*
+ * The first argument of the FLT_OTEL_PARSE_SCOPE_STATUS_DEF() macro is defined
+ * as otelc_span_status_t in <opentelemetry-c-wrapper/span.h> .
+ */
+#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",   " <name>")                                            \
+       FLT_OTEL_PARSE_SCOPE_DEF(        SPAN, 0, NONE, 2, 7, "span",         " <name> [<reference>] [root]")                       \
+       FLT_OTEL_PARSE_SCOPE_DEF(   ATTRIBUTE, 1, NONE, 3, 0,   "attribute",  " <key> <sample> ...")                                \
+       FLT_OTEL_PARSE_SCOPE_DEF(       EVENT, 1, NONE, 4, 0,   "event",      " <name> <key> <sample> ...")                         \
+       FLT_OTEL_PARSE_SCOPE_DEF(     BAGGAGE, 1,  VAR, 3, 0,   "baggage",    " <key> <sample> ...")                                \
+       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",     " <code> [<sample> ...]")                             \
+       FLT_OTEL_PARSE_SCOPE_DEF(      FINISH, 0, NONE, 2, 0,   "finish",     " <name> ...")                                        \
+       FLT_OTEL_PARSE_SCOPE_DEF(IDLE_TIMEOUT, 0, NONE, 2, 2, "idle-timeout", " <time>")                                            \
+       FLT_OTEL_PARSE_SCOPE_DEF(         ACL, 0, CHAR, 3, 0, "acl",          " <name> <criterion> [flags] [operator] <value> ...") \
+       FLT_OTEL_PARSE_SCOPE_DEF(    ON_EVENT, 0, NONE, 2, 0, "otel-event",   " <name> [{ if | unless } <condition>]")
+
+/* Invalid character check modes for identifier validation. */
+enum FLT_OTEL_PARSE_INVCHAR_enum {
+       FLT_OTEL_PARSE_INVALID_NONE,
+       FLT_OTEL_PARSE_INVALID_CHAR,
+       FLT_OTEL_PARSE_INVALID_DOM,
+       FLT_OTEL_PARSE_INVALID_CTX,
+       FLT_OTEL_PARSE_INVALID_VAR,
+};
+
+enum FLT_OTEL_PARSE_INSTR_enum {
+#define FLT_OTEL_PARSE_INSTR_DEF(a,b,c,d,e,f,g)   FLT_OTEL_PARSE_INSTR_##a,
+       FLT_OTEL_PARSE_INSTR_DEFINES
+#undef FLT_OTEL_PARSE_INSTR_DEF
+};
+
+enum FLT_OTEL_PARSE_GROUP_enum {
+#define FLT_OTEL_PARSE_GROUP_DEF(a,b,c,d,e,f,g)   FLT_OTEL_PARSE_GROUP_##a,
+       FLT_OTEL_PARSE_GROUP_DEFINES
+#undef FLT_OTEL_PARSE_GROUP_DEF
+};
+
+enum FLT_OTEL_PARSE_SCOPE_enum {
+#define FLT_OTEL_PARSE_SCOPE_DEF(a,b,c,d,e,f,g)   FLT_OTEL_PARSE_SCOPE_##a,
+       FLT_OTEL_PARSE_SCOPE_DEFINES
+#undef FLT_OTEL_PARSE_SCOPE_DEF
+};
+
+/* Context storage type flags for inject/extract operations. */
+enum FLT_OTEL_CTX_USE_enum {
+       FLT_OTEL_CTX_USE_HEADERS = 1 << 0,
+};
+
+/* Logging state flags for the OTel filter. */
+enum FLT_OTEL_LOGGING_enum {
+       FLT_OTEL_LOGGING_OFF       = 0,
+       FLT_OTEL_LOGGING_ON        = 1 << 0,
+       FLT_OTEL_LOGGING_NOLOGNORM = 1 << 1,
+};
+
+/* Keyword metadata used by the configuration section parsers. */
+struct flt_otel_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_OTEL_PARSE_KEYWORD(n,s)           (strcmp(args[n], (s)) == 0)
+
+#define FLT_OTEL_PARSE_WARNING(f, ...) \
+       ha_warning("parsing [%s:%d] : " FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME "'" f "'\n", ##__VA_ARGS__);
+
+#define FLT_OTEL_PARSE_ALERT(f, ...)                                                                           \
+       do {                                                                                                   \
+               ha_alert("parsing [%s:%d] : " FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME "'" f "'\n", ##__VA_ARGS__); \
+                                                                                                              \
+               retval |= ERR_ABORT | ERR_ALERT;                                                               \
+       } while (0)
+
+#define FLT_OTEL_POST_PARSE_ALERT(f, ...) \
+       FLT_OTEL_PARSE_ALERT(f, flt_otel_current_config->cfg_file, ##__VA_ARGS__)
+
+#define FLT_OTEL_PARSE_ERR(e,f, ...)                            \
+       do {                                                    \
+               if (*(e) == NULL)                               \
+                       (void)memprintf((e), f, ##__VA_ARGS__); \
+                                                               \
+               retval |= ERR_ABORT | ERR_ALERT;                \
+       } while (0)
+
+#define FLT_OTEL_PARSE_IFERR_ALERT()                         \
+       do {                                                 \
+               if (err == NULL)                             \
+                       break;                               \
+                                                            \
+               FLT_OTEL_PARSE_ALERT("%s", file, line, err); \
+               FLT_OTEL_ERR_FREE(err);                      \
+       } while (0)
 
 #endif /* _OTEL_PARSER_H_ */
 
index ae230da2118674444775710be3941ed4f6c3086e..9833d8b55fd46d52d43e73a8d9f708487f166a6e 100644 (file)
@@ -34,6 +34,12 @@ int         flt_otel_args_count(const char **args);
 /* Concatenate argument array elements into a single string. */
 int         flt_otel_args_concat(const char **args, int idx, int n, char **str);
 
+/* Parse a string to double with range validation. */
+bool        flt_otel_strtod(const char *nptr, double *value, double limit_min, double limit_max, char **err);
+
+/* Parse a string to int64_t with range validation. */
+bool        flt_otel_strtoll(const char *nptr, int64_t *value, int64_t limit_min, int64_t limit_max, char **err);
+
 /* Convert sample data to a string representation. */
 int         flt_otel_sample_to_str(const struct sample_data *data, char *value, size_t size, char **err);
 
diff --git a/addons/otel/src/event.c b/addons/otel/src/event.c
new file mode 100644 (file)
index 0000000..00a838b
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "../include/include.h"
+
+
+/* Event data table built from the X-macro list. */
+#define FLT_OTEL_EVENT_DEF(a,b,c,d,e,f)   { AN_##b##_##a, OTELC_STRINGIFY_ARG(AN_##b##_##a), SMP_OPT_DIR_##b, SMP_VAL_FE_##c, SMP_VAL_BE_##d, e, f },
+const struct flt_otel_event_data flt_otel_event_data[FLT_OTEL_EVENT_MAX] = { FLT_OTEL_EVENT_DEFINES };
+#undef FLT_OTEL_EVENT_DEF
+
+
+/*
+ * Local variables:
+ *  c-indent-level: 8
+ *  c-basic-offset: 8
+ * End:
+ *
+ * vi: noexpandtab shiftwidth=8 tabstop=8
+ */
index 4f78c90f123e0501001e227f4d9524055d106f93..bcc815ee8e95795a256adf14f4d7cc9e895b7f33 100644 (file)
@@ -8,6 +8,1261 @@ static struct otelc_dbg_mem_data dbg_mem_data[1000000];
 static struct otelc_dbg_mem      dbg_mem;
 #endif
 
+static struct flt_otel_conf       *flt_otel_current_config = NULL;
+static struct flt_otel_conf_instr *flt_otel_current_instr = NULL;
+static struct flt_otel_conf_group *flt_otel_current_group = NULL;
+static struct flt_otel_conf_scope *flt_otel_current_scope = NULL;
+static struct flt_otel_conf_span  *flt_otel_current_span = NULL;
+
+
+/***
+ * NAME
+ *   flt_otel_parse_strdup - string duplication with error handling
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_strdup(char **dst, size_t *dst_len, const char *src, char **err, const char *err_msg)
+ *
+ * ARGUMENTS
+ *   dst     - pointer to the destination string pointer
+ *   dst_len - optional pointer to store the duplicated string length
+ *   src     - source string to duplicate
+ *   err     - indirect pointer to error message string
+ *   err_msg - context label used in error messages
+ *
+ * DESCRIPTION
+ *   Duplicates the string <src> into <*dst> with error handling.  Optionally
+ *   stores the string length in <dst_len>.  On failure, an error message is
+ *   formatted using <err_msg> as context.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_strdup(char **dst, size_t *dst_len, const char *src, char **err, const char *err_msg)
+{
+       int retval = ERR_NONE;
+
+       OTELC_FUNC("%p:%p, %p, %p, %p:%p, \"%s\"", OTELC_DPTR_ARGS(dst), dst_len, src, OTELC_DPTR_ARGS(err), OTELC_STR_ARG(err_msg));
+
+       /* dst_len is not set if the string has not been copied. */
+       *dst = OTELC_STRDUP(src);
+       if (*dst == NULL)
+               FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", err_msg);
+       else if (dst_len != NULL)
+               *dst_len = strlen(*dst);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_keyword - keyword argument parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_keyword(char **ptr, char **args, int cur_arg, int pos, char **err, const char *err_msg)
+ *
+ * ARGUMENTS
+ *   ptr     - pointer to the destination string pointer
+ *   args    - configuration line arguments array
+ *   cur_arg - current argument index for error reporting
+ *   pos     - position of the keyword in <args>
+ *   err     - indirect pointer to error message string
+ *   err_msg - context label used in error messages
+ *
+ * DESCRIPTION
+ *   Parses a single keyword argument from the configuration line.  Checks
+ *   that the keyword has not already been set and that a value is present
+ *   at position <pos> + 1 in <args>.  The value is duplicated via
+ *   flt_otel_parse_strdup() into <*ptr>.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_keyword(char **ptr, char **args, int cur_arg, int pos, char **err, const char *err_msg)
+{
+       int retval = ERR_NONE;
+
+       OTELC_FUNC("%p:%p, %p, %d, %d, %p:%p, \"%s\"", OTELC_DPTR_ARGS(ptr), args, cur_arg, pos, OTELC_DPTR_ARGS(err), OTELC_STR_ARG(err_msg));
+
+       /* Reject duplicate keyword assignments. */
+       if (*ptr != NULL) {
+               if (cur_arg == pos)
+                       FLT_OTEL_PARSE_ERR(err, FLT_OTEL_FMT_TYPE "%s already set", err_msg);
+               else
+                       FLT_OTEL_PARSE_ERR(err, "'%s' : %s already set", args[cur_arg], err_msg);
+       }
+       else if (!FLT_OTEL_ARG_ISVALID(pos + 1)) {
+               if (cur_arg == pos)
+                       FLT_OTEL_PARSE_ERR(err, FLT_OTEL_FMT_TYPE "no %s set", err_msg);
+               else
+                       FLT_OTEL_PARSE_ERR(err, "'%s' : no %s set", args[cur_arg], err_msg);
+       }
+       else {
+               retval = flt_otel_parse_strdup(ptr, NULL, args[pos + 1], err, args[cur_arg]);
+       }
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_invalid_char - name character validation
+ *
+ * SYNOPSIS
+ *   static const char *flt_otel_parse_invalid_char(const char *name, int type)
+ *
+ * ARGUMENTS
+ *   name - string to validate
+ *   type - validation type selector
+ *
+ * DESCRIPTION
+ *   Validates characters in a <name> string according to the specified <type>.
+ *   Uses HAProxy's invalid_char() for identifiers, invalid_domainchar() for
+ *   domains, invalid_prefix_char() for context prefixes, and a custom
+ *   alphanumeric check for variables.
+ *
+ * RETURN VALUE
+ *   Returns a pointer to the first invalid character in <name>,
+ *   or NULL if all characters are valid.
+ */
+static const char *flt_otel_parse_invalid_char(const char *name, int type)
+{
+       const char *retptr = NULL;
+
+       OTELC_FUNC("\"%s\", %d", OTELC_STR_ARG(name), type);
+
+       if (!OTELC_STR_IS_VALID(name))
+               OTELC_RETURN_EX(retptr, const char *, "%p");
+
+       /* Dispatch to the appropriate character validation function. */
+       if (type == FLT_OTEL_PARSE_INVALID_CHAR) {
+               retptr = invalid_char(name);
+       }
+       else if (type == FLT_OTEL_PARSE_INVALID_DOM) {
+               retptr = invalid_domainchar(name);
+       }
+       else if (type == FLT_OTEL_PARSE_INVALID_CTX) {
+               retptr = invalid_prefix_char(name);
+       }
+       else if (type == FLT_OTEL_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;
+       }
+
+       OTELC_RETURN_EX(retptr, const char *, "%p");
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_check - configuration keyword validation
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_check(const char *file, int line, char **args, const void *id, const struct flt_otel_parse_data *parse_data, size_t parse_data_size, const struct flt_otel_parse_data **pdata, char **err)
+ *
+ * ARGUMENTS
+ *   file            - configuration file path
+ *   line            - configuration file line number
+ *   args            - configuration line arguments array
+ *   id              - parent section identifier
+ *   parse_data      - keyword definition table
+ *   parse_data_size - number of entries in <parse_data>
+ *   pdata           - output pointer to the matched keyword entry
+ *   err             - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Common validation for configuration keywords.  Looks up <args[0]> in the
+ *   <parse_data> table, checks argument count bounds, validates the first
+ *   argument's characters according to the keyword's check_name type, and
+ *   verifies that the parent section ID is set when required.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_check(const char *file, int line, char **args, const void *id, const struct flt_otel_parse_data *parse_data, size_t parse_data_size, const struct flt_otel_parse_data **pdata, char **err)
+{
+       int i, argc, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, %p, %p, %zu, %p:%p, %p:%p", OTELC_STR_ARG(file), line, args, id, parse_data, parse_data_size, OTELC_DPTR_ARGS(pdata), OTELC_DPTR_ARGS(err));
+
+       FLT_OTEL_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 (FLT_OTEL_PARSE_KEYWORD(0, parse_data[i].name))
+                       *pdata = parse_data + i;
+
+       if (*pdata == NULL)
+               FLT_OTEL_PARSE_ERR(err, "'%s' : unknown keyword", args[0]);
+       else
+               argc = flt_otel_args_count((const char **)args);
+
+       if ((retval & ERR_CODE) || (id == NULL))
+               /* Do nothing. */;
+       else if ((id != flt_otel_current_instr) && (flt_otel_current_config->instr == NULL))
+               FLT_OTEL_PARSE_ERR(err, "instrumentation 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_OTEL_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_OTEL_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_OTEL_PARSE_INVALID_NONE)) {
+               const char *ic;
+
+               ic = flt_otel_parse_invalid_char(args[1], (*pdata)->check_name);
+               if (ic != NULL)
+                       FLT_OTEL_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_OTEL_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);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_sample_expr - sample expression parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_sample_expr(const char *file, int line, char **args, int *idx, struct list *head, char **err)
+ *
+ * ARGUMENTS
+ *   file - configuration file path
+ *   line - configuration file line number
+ *   args - configuration line arguments array
+ *   idx  - pointer to the current position in <args>
+ *   head - list head for parsed sample expressions
+ *   err  - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses a single HAProxy sample expression at position <*idx> in <args>.
+ *   Creates a conf_sample_expr structure and calls sample_parse_expr() to
+ *   compile the expression.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_sample_expr(const char *file, int line, char **args, int *idx, struct list *head, char **err)
+{
+       struct flt_otel_conf_sample_expr *expr;
+       int                             retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, idx, head, OTELC_DPTR_ARGS(err));
+
+       expr = flt_otel_conf_sample_expr_init(args[*idx], line, head, err);
+       if (expr != NULL) {
+               expr->expr = sample_parse_expr(args, idx, file, line, err, &(flt_otel_current_config->proxy->conf.args), NULL);
+               if (expr->expr != NULL)
+                       OTELC_DBG(DEBUG, "sample expression '%s' added", expr->fmt_expr);
+               else
+                       retval |= ERR_ABORT | ERR_ALERT;
+       } else {
+               retval |= ERR_ABORT | ERR_ALERT;
+       }
+
+       if (retval & ERR_CODE)
+               flt_otel_conf_sample_expr_free(&expr);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_sample - sample definition parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_sample(const char *file, int line, char **args, int idx, int n, const struct otelc_value *extra, struct list *head, char **err)
+ *
+ * ARGUMENTS
+ *   file  - configuration file path
+ *   line  - configuration file line number
+ *   args  - configuration line arguments array
+ *   idx   - args[] position where the sample value starts
+ *   n     - maximum number of sample expressions to parse (0 means unlimited)
+ *   extra - optional extra data (event name or status code)
+ *   head  - list head for parsed sample definitions
+ *   err   - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses a complete sample definition starting at index <idx> in <args>.
+ *   Creates a conf_sample structure with optional <extra> data (event name or
+ *   status code), then parses sample expressions.  When <n> is 0, all remaining
+ *   arguments are parsed; otherwise at most <n> expressions are parsed.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success, or a combination of ERR_* flags
+ *   if an error is encountered.
+ */
+static int flt_otel_parse_cfg_sample(const char *file, int line, char **args, int idx, int n, const struct otelc_value *extra, struct list *head, char **err)
+{
+       struct flt_otel_conf_sample *sample;
+       int                          retval = ERR_NONE;
+       int                          count = 0;
+
+       OTELC_FUNC("\"%s\", %d, %p, %d, %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, idx, n, extra, head, OTELC_DPTR_ARGS(err));
+
+       sample = flt_otel_conf_sample_init_ex((const char **)args, idx, n, extra, line, head, err);
+       if (sample == NULL)
+               FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[0]);
+
+       if (!(retval & ERR_CODE)) {
+               flt_otel_current_config->proxy->conf.args.ctx  = ARGC_OTEL;
+               flt_otel_current_config->proxy->conf.args.file = file;
+               flt_otel_current_config->proxy->conf.args.line = line;
+
+               while (!(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(idx) && ((n == 0) || (count < n))) {
+                       retval = flt_otel_parse_cfg_sample_expr(file, line, args, &idx, &(sample->exprs), err);
+                       if (!(retval & ERR_CODE))
+                               count++;
+               }
+
+               flt_otel_current_config->proxy->conf.args.file = NULL;
+               flt_otel_current_config->proxy->conf.args.line = 0;
+
+               OTELC_DBG(DEBUG, "sample '%s' -> '%s' added, (%d %d)", sample->key, sample->fmt_string, sample->num_exprs, count);
+       }
+
+       if (retval & ERR_CODE)
+               flt_otel_conf_sample_free(&sample);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_str - string list parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_str(const char *file, int line, char **args, struct list *head, char **err)
+ *
+ * ARGUMENTS
+ *   file - configuration file path
+ *   line - configuration file line number
+ *   args - configuration line arguments array
+ *   head - list head for parsed string entries
+ *   err  - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses one or more string arguments into a conf_str list.  All arguments
+ *   starting from index 1 are added to <head>.  Used for the "finish" keyword.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_str(const char *file, int line, char **args, struct list *head, char **err)
+{
+       int i, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p", OTELC_STR_ARG(file), line, args, head, OTELC_DPTR_ARGS(err));
+
+       for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
+               if (flt_otel_conf_str_init(args[i], line, head, err) == NULL)
+                       retval |= ERR_ABORT | ERR_ALERT;
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_file - file path argument parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_file(char **ptr, const char *file, int line, char **args, char **err, const char *err_msg)
+ *
+ * ARGUMENTS
+ *   ptr     - pointer to the destination file path string pointer
+ *   file    - configuration file path
+ *   line    - configuration file line number
+ *   args    - configuration line arguments array
+ *   err     - indirect pointer to error message string
+ *   err_msg - context label used in error messages
+ *
+ * DESCRIPTION
+ *   Parses and validates a file path argument.  Checks that the argument is
+ *   present, that no extra arguments follow, and that the file exists and is
+ *   readable.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_file(char **ptr, const char *file, int line, char **args, char **err, const char *err_msg)
+{
+       int retval = ERR_NONE;
+
+       OTELC_FUNC("%p:%p, \"%s\", %d, %p, %p:%p, \"%s\"", OTELC_DPTR_ARGS(ptr), OTELC_STR_ARG(file), line, args, OTELC_DPTR_ARGS(err), err_msg);
+
+       if (!FLT_OTEL_ARG_ISVALID(1))
+               FLT_OTEL_PARSE_ERR(err, "'%s' : no %s specified", flt_otel_current_instr->id, err_msg);
+       else if (alertif_too_many_args(1, file, line, args, &retval))
+               retval |= ERR_ABORT | ERR_ALERT;
+       else if (access(args[1], R_OK) == -1)
+               FLT_OTEL_PARSE_ERR(err, "'%s' : %s", args[1], strerror(errno));
+       else
+               retval = flt_otel_parse_keyword(ptr, args, 0, 0, err, err_msg);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_check_scope - configuration scope filter
+ *
+ * SYNOPSIS
+ *   static bool flt_otel_parse_check_scope(void)
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   Checks whether the current configuration parsing is within the correct
+ *   HAProxy cfg_scope filter.  When cfg_scope is set and does not match the
+ *   current filter ID, the configuration line is skipped.
+ *
+ * RETURN VALUE
+ *   Returns TRUE in case the configuration is not in the currently
+ *   defined scope, FALSE otherwise.
+ */
+static bool flt_otel_parse_check_scope(void)
+{
+       bool retval = 0;
+
+       if ((cfg_scope != NULL) && (flt_otel_current_config->id != NULL) && (strcmp(flt_otel_current_config->id, cfg_scope) != 0)) {
+               OTELC_DBG(INFO, "cfg_scope: '%s', id: '%s'", cfg_scope, flt_otel_current_config->id);
+
+               retval = 1;
+       }
+
+       return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_instr - otel-instrumentation section parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_instr(const char *file, int line, char **args, int kw_mod)
+ *
+ * ARGUMENTS
+ *   file   - configuration file path
+ *   line   - configuration file line number
+ *   args   - configuration line arguments array
+ *   kw_mod - keyword modifier flags (e.g. KWM_NO)
+ *
+ * DESCRIPTION
+ *   Section parser for the otel-instrumentation configuration block.  Handles
+ *   keywords: instrumentation ID, log, config, groups, scopes, acl, rate-limit,
+ *   option (disabled/hard-errors/nolognorm), and debug-level.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_instr(const char *file, int line, char **args, int kw_mod)
+{
+#define FLT_OTEL_PARSE_INSTR_DEF(a,b,c,d,e,f,g)   { FLT_OTEL_PARSE_INSTR_##a, b, FLT_OTEL_PARSE_INVALID_##c, d, e, f, g },
+       static const struct flt_otel_parse_data  parse_data[] = { FLT_OTEL_PARSE_INSTR_DEFINES };
+#undef FLT_OTEL_PARSE_INSTR_DEF
+       const struct flt_otel_parse_data        *pdata = NULL;
+       char                                    *err = NULL, *err_log = NULL;
+       int                                      i, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, 0x%08x", OTELC_STR_ARG(file), line, args, kw_mod);
+
+       if (flt_otel_parse_check_scope())
+               OTELC_RETURN_INT(retval);
+
+       /* Validate and identify the instrumentation keyword. */
+       retval = flt_otel_parse_cfg_check(file, line, args, flt_otel_current_instr, parse_data, OTELC_TABLESIZE(parse_data), &pdata, &err);
+       if (retval & ERR_CODE) {
+               FLT_OTEL_PARSE_IFERR_ALERT();
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       /* Handle keyword-specific instrumentation configuration. */
+       if (pdata->keyword == FLT_OTEL_PARSE_INSTR_ID) {
+               if (flt_otel_current_config->instr != NULL) {
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : instrumentation can be defined only once", args[1]);
+               } else {
+                       flt_otel_current_instr = flt_otel_conf_instr_init(args[1], line, NULL, &err);
+                       if (flt_otel_current_instr == NULL)
+                               retval |= ERR_ABORT | ERR_ALERT;
+               }
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_LOG) {
+               if (parse_logger(args, &(flt_otel_current_instr->proxy_log.loggers), kw_mod == KWM_NO, file, line, &err_log) == 0) {
+                       FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : %s", args[0], args[1], err_log);
+                       OTELC_SFREE_CLEAR(err_log);
+               } else {
+                       flt_otel_current_instr->logging |= FLT_OTEL_LOGGING_ON;
+               }
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_CONFIG) {
+               retval = flt_otel_parse_cfg_file(&(flt_otel_current_instr->config), file, line, args, &err, "configuration file");
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_GROUPS) {
+               for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
+                       if (flt_otel_conf_ph_init(args[i], line, &(flt_otel_current_instr->ph_groups), &err) == NULL)
+                               retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_SCOPES) {
+               for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
+                       if (flt_otel_conf_ph_init(args[i], line, &(flt_otel_current_instr->ph_scopes), &err) == NULL)
+                               retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_ACL) {
+               if (FLT_OTEL_PARSE_KEYWORD(1, "or"))
+                       FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
+               else if (parse_acl((const char **)args + 1, &(flt_otel_current_instr->acls), &err, &(flt_otel_current_config->proxy->conf.args), file, line) == NULL)
+                       retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_RATE_LIMIT) {
+               double value;
+
+               if (flt_otel_strtod(args[1], &value, 0.0, 100.0, &err))
+                       flt_otel_current_instr->rate_limit = FLT_OTEL_FLOAT_U32(value);
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_OPTION) {
+               if (FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_OPTION_DISABLED)) {
+                       flt_otel_current_instr->flag_disabled = (kw_mod == KWM_NO) ? 0 : 1;
+               }
+               else if (FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_OPTION_HARDERR)) {
+                       flt_otel_current_instr->flag_harderr = (kw_mod == KWM_NO) ? 0 : 1;
+               }
+               else if (FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_OPTION_NOLOGNORM)) {
+                       if (kw_mod == KWM_NO)
+                               flt_otel_current_instr->logging &= ~FLT_OTEL_LOGGING_NOLOGNORM;
+                       else
+                               flt_otel_current_instr->logging |= FLT_OTEL_LOGGING_NOLOGNORM;
+               }
+               else
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : unknown option '%s'", args[0], args[1]);
+       }
+#ifdef DEBUG_OTEL
+       else if (pdata->keyword == FLT_OTEL_PARSE_INSTR_DEBUG_LEVEL) {
+               int64_t value;
+
+               if (flt_otel_strtoll(args[1], &value, 0, OTELC_DBG_LEVEL_MASK, &err))
+                       otelc_dbg_level = value;
+       }
+#else
+       else {
+               FLT_OTEL_PARSE_WARNING("'%s' : keyword ignored", file, line, args[0]);
+       }
+#endif
+
+       FLT_OTEL_PARSE_IFERR_ALERT();
+
+       if ((retval & ERR_CODE) && (flt_otel_current_instr != NULL))
+               flt_otel_conf_instr_free(&flt_otel_current_instr);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_post_parse_cfg_instr - otel-instrumentation post-parse check
+ *
+ * SYNOPSIS
+ *   static int flt_otel_post_parse_cfg_instr(void)
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   Post-parse callback for the otel-instrumentation section.  Links the parsed
+ *   instrumentation structure to the filter configuration and verifies that a
+ *   configuration file path is specified.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_post_parse_cfg_instr(void)
+{
+       int retval = ERR_NONE;
+
+       OTELC_FUNC("");
+
+       if (flt_otel_current_instr == NULL)
+               OTELC_RETURN_INT(retval);
+
+       flt_otel_current_config->instr = flt_otel_current_instr;
+
+       if (flt_otel_current_instr->id == NULL)
+               OTELC_RETURN_INT(retval);
+
+       if (flt_otel_current_instr->config == NULL)
+               FLT_OTEL_POST_PARSE_ALERT("instrumentation '%s' has no configuration file specified", flt_otel_current_instr->cfg_line, flt_otel_current_instr->id);
+
+       flt_otel_current_instr = NULL;
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_group - otel-group section parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_group(const char *file, int line, char **args, int kw_mod)
+ *
+ * ARGUMENTS
+ *   file   - configuration file path
+ *   line   - configuration file line number
+ *   args   - configuration line arguments array
+ *   kw_mod - keyword modifier flags (e.g. KWM_NO)
+ *
+ * DESCRIPTION
+ *   Section parser for the otel-group configuration block.  Handles keywords:
+ *   group ID and scopes.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_group(const char *file, int line, char **args, int kw_mod)
+{
+#define FLT_OTEL_PARSE_GROUP_DEF(a,b,c,d,e,f,g)   { FLT_OTEL_PARSE_GROUP_##a, b, FLT_OTEL_PARSE_INVALID_##c, d, e, f, g },
+       static const struct flt_otel_parse_data  parse_data[] = { FLT_OTEL_PARSE_GROUP_DEFINES };
+#undef FLT_OTEL_PARSE_GROUP_DEF
+       const struct flt_otel_parse_data        *pdata = NULL;
+       char                                    *err = NULL;
+       int                                      i, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, 0x%08x", OTELC_STR_ARG(file), line, args, kw_mod);
+
+       if (flt_otel_parse_check_scope())
+               OTELC_RETURN_INT(retval);
+
+       /* Validate and identify the group keyword. */
+       retval = flt_otel_parse_cfg_check(file, line, args, flt_otel_current_group, parse_data, OTELC_TABLESIZE(parse_data), &pdata, &err);
+       if (retval & ERR_CODE) {
+               FLT_OTEL_PARSE_IFERR_ALERT();
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       /* Handle keyword-specific group configuration. */
+       if (pdata->keyword == FLT_OTEL_PARSE_GROUP_ID) {
+               flt_otel_current_group = flt_otel_conf_group_init(args[1], line, &(flt_otel_current_config->groups), &err);
+               if (flt_otel_current_group == NULL)
+                       retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_GROUP_SCOPES) {
+               for (i = 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(i); i++)
+                       if (flt_otel_conf_ph_init(args[i], line, &(flt_otel_current_group->ph_scopes), &err) == NULL)
+                               retval |= ERR_ABORT | ERR_ALERT;
+       }
+
+       FLT_OTEL_PARSE_IFERR_ALERT();
+
+       if ((retval & ERR_CODE) && (flt_otel_current_group != NULL))
+               flt_otel_conf_group_free(&flt_otel_current_group);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_post_parse_cfg_group - otel-group post-parse check
+ *
+ * SYNOPSIS
+ *   static int flt_otel_post_parse_cfg_group(void)
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   Post-parse callback for the otel-group section.  Verifies that at least one
+ *   scope is defined in the group.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_post_parse_cfg_group(void)
+{
+       int retval = ERR_NONE;
+
+       OTELC_FUNC("");
+
+       if (flt_otel_current_group == NULL)
+               OTELC_RETURN_INT(retval);
+
+       /* Check that the group has at least one scope defined. */
+       if (LIST_ISEMPTY(&(flt_otel_current_group->ph_scopes)))
+               FLT_OTEL_POST_PARSE_ALERT("group '%s' has no defined scope(s)", flt_otel_current_group->cfg_line, flt_otel_current_group->id);
+
+       flt_otel_current_group = NULL;
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_scope_ctx - context storage type parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_scope_ctx(char **args, int cur_arg, char **err)
+ *
+ * ARGUMENTS
+ *   args    - configuration line arguments array
+ *   cur_arg - index of the storage type argument in <args>
+ *   err     - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses the context storage type argument for inject/extract keywords.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_scope_ctx(char **args, int cur_arg, char **err)
+{
+       uint8_t flags = 0;
+       int     retval = ERR_NONE;
+
+       OTELC_FUNC("%p, %d, %p:%p", args, cur_arg, OTELC_DPTR_ARGS(err));
+
+       if (FLT_OTEL_PARSE_KEYWORD(cur_arg, FLT_OTEL_PARSE_CTX_USE_HEADERS))
+               flags = FLT_OTEL_CTX_USE_HEADERS;
+       else
+               FLT_OTEL_PARSE_ERR(err, "'%s' : invalid context storage type", args[0]);
+
+       if (flags == 0)
+               /* Do nothing. */;
+       else if (flt_otel_current_span->ctx_flags & flags)
+               FLT_OTEL_PARSE_ERR(err, "'%s' : %s already used", args[0], args[cur_arg]);
+       else
+               flt_otel_current_span->ctx_flags |= flags;
+
+       OTELC_DBG(NOTICE, "ctx_flags: 0x%02hhx (0x%02hhx)", flt_otel_current_span->ctx_flags, flags);
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_acl - ACL condition builder
+ *
+ * SYNOPSIS
+ *   static struct acl_cond *flt_otel_parse_acl(const char *file, int line, struct proxy *px, const char **args, char **err, struct list *head, ...)
+ *
+ * ARGUMENTS
+ *   file - configuration file path
+ *   line - configuration file line number
+ *   px   - proxy instance for ACL resolution
+ *   args - condition arguments (if/unless followed by ACL names)
+ *   err  - indirect pointer to error message string
+ *   head - first ACL list head to search
+ *
+ * DESCRIPTION
+ *   Builds an ACL condition by trying multiple ACL lists in order.  The
+ *   variadic arguments provide a sequence of ACL list heads to search; the
+ *   first successful build_acl_cond() result is returned.
+ *
+ * RETURN VALUE
+ *   Returns a pointer to the built ACL condition, or NULL if no condition could
+ *   be built from any of the provided lists.
+ */
+static struct acl_cond *flt_otel_parse_acl(const char *file, int line, struct proxy *px, const char **args, char **err, struct list *head, ...)
+{
+       va_list          ap;
+       int              n = 0;
+       struct acl_cond *retptr = NULL;
+
+       OTELC_FUNC("\"%s\", %d, %p, %p, %p:%p, %p, ...", OTELC_STR_ARG(file), line, px, args, OTELC_DPTR_ARGS(err), head);
+
+       /* Try each ACL list in order until a condition is built. */
+       for (va_start(ap, head); (retptr == NULL) && (head != NULL); head = va_arg(ap, typeof(head)), n++) {
+               retptr = build_acl_cond(file, line, head, px, args, (n == 0) ? err : NULL);
+               if (retptr != NULL)
+                       OTELC_DBG(NOTICE, "ACL build done, using list %p %d", head, n);
+       }
+       va_end(ap);
+
+       if ((retptr != NULL) && (err != NULL))
+               ha_free(err);
+
+       OTELC_RETURN_PTR(retptr);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg_scope - otel-scope section parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int kw_mod)
+ *
+ * ARGUMENTS
+ *   file   - configuration file path
+ *   line   - configuration file line number
+ *   args   - configuration line arguments array
+ *   kw_mod - keyword modifier flags (e.g. KWM_NO)
+ *
+ * DESCRIPTION
+ *   Section parser for the otel-scope configuration block.  Handles keywords:
+ *   scope ID, span (with optional root/parent/link modifiers), link, attribute,
+ *   event, baggage, status, inject, extract, finish, instrument, acl, and
+ *   otel-event (with optional if/unless conditions).
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg_scope(const char *file, int line, char **args, int kw_mod)
+{
+#define FLT_OTEL_PARSE_SCOPE_DEF(a,b,c,d,e,f,g)   { FLT_OTEL_PARSE_SCOPE_##a, b, FLT_OTEL_PARSE_INVALID_##c, d, e, f, g },
+       static const struct flt_otel_parse_data  parse_data[] = { FLT_OTEL_PARSE_SCOPE_DEFINES };
+#undef FLT_OTEL_PARSE_SCOPE_DEF
+       const struct flt_otel_parse_data        *pdata = NULL;
+       char                                    *err = NULL;
+       int                                      i, retval = ERR_NONE;
+
+       OTELC_FUNC("\"%s\", %d, %p, 0x%08x", OTELC_STR_ARG(file), line, args, kw_mod);
+
+       if (flt_otel_parse_check_scope())
+               OTELC_RETURN_INT(retval);
+
+       /* Validate and identify the scope keyword. */
+       retval = flt_otel_parse_cfg_check(file, line, args, flt_otel_current_span, parse_data, OTELC_TABLESIZE(parse_data), &pdata, &err);
+       if (retval & ERR_CODE) {
+               FLT_OTEL_PARSE_IFERR_ALERT();
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       /* Handle keyword-specific scope configuration. */
+       if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ID) {
+               /* Initialization of a new scope. */
+               flt_otel_current_scope = flt_otel_conf_scope_init(args[1], line, &(flt_otel_current_config->scopes), &err);
+               if (flt_otel_current_scope == NULL)
+                       retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_SPAN) {
+               /*
+                * Checking if this is the beginning of the definition of
+                * a new span.
+                */
+               if (flt_otel_current_span != NULL) {
+                       OTELC_DBG(DEBUG, "span '%s' (done)", flt_otel_current_span->id);
+
+                       flt_otel_current_span = NULL;
+               }
+
+               /* Initialization of a new span. */
+               flt_otel_current_span = flt_otel_conf_span_init(args[1], line, &(flt_otel_current_scope->spans), &err);
+
+               /*
+                * In case the span has a defined reference (parent), the
+                * correctness of the arguments is checked here.
+                */
+               if (flt_otel_current_span == NULL) {
+                       retval |= ERR_ABORT | ERR_ALERT;
+               }
+               else if (FLT_OTEL_ARG_ISVALID(2)) {
+                       for (i = 2; (i < pdata->args_max) && FLT_OTEL_ARG_ISVALID(i); i++) {
+                               if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_ROOT)) {
+                                       if (flt_otel_current_span->flag_root)
+                                               FLT_OTEL_PARSE_ERR(&err, "'%s' : already set (use '%s%s')", args[i], pdata->name, pdata->usage);
+                                       else
+                                               flt_otel_current_span->flag_root = 1;
+                               }
+                               else if (FLT_OTEL_PARSE_KEYWORD(i, FLT_OTEL_PARSE_SPAN_PARENT)) {
+                                       if (FLT_OTEL_ARG_ISVALID(i + 1))
+                                               retval |= flt_otel_parse_strdup(&(flt_otel_current_span->ref_id), &(flt_otel_current_span->ref_id_len), args[++i], &err, args[1]);
+                                       else
+                                               FLT_OTEL_PARSE_ERR(&err, "'%s' : too few arguments (use '%s%s')", args[i], pdata->name, pdata->usage);
+                               }
+                               else {
+                                       FLT_OTEL_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.
+                        */
+                       OTELC_DBG(DEBUG, "new span '%s' without reference", flt_otel_current_span->id);
+               }
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ATTRIBUTE) {
+               retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, NULL, &(flt_otel_current_span->attributes), &err);
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_EVENT) {
+               struct otelc_value extra = { .u_type = OTELC_VALUE_STRING, .u.value_string = args[1] };
+
+               retval = flt_otel_parse_cfg_sample(file, line, args, 3, 0, &extra, &(flt_otel_current_span->events), &err);
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_BAGGAGE) {
+               retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, NULL, &(flt_otel_current_span->baggages), &err);
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_STATUS) {
+#define FLT_OTEL_PARSE_SCOPE_STATUS_DEF(a,b)   { OTELC_SPAN_STATUS_##a, b },
+               static const struct {
+                       int         code;
+                       const char *keyword;
+               } status[] = { FLT_OTEL_PARSE_SCOPE_STATUS_DEFINES };
+#undef FLT_OTEL_PARSE_SCOPE_STATUS_DEF
+
+               for (i = 0; i < OTELC_TABLESIZE(status); i++)
+                       if (FLT_OTEL_PARSE_KEYWORD(1, status[i].keyword)) {
+                               OTELC_DBG(DEBUG, "span status: %d '%s'", status[i].code, status[i].keyword);
+
+                               break;
+                       }
+
+               /*
+                * Regardless of the use of the list, only one status per event
+                * is allowed.
+                */
+               if (i >= OTELC_TABLESIZE(status)) {
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid span status", args[1]);
+               }
+               else if (LIST_ISEMPTY(&(flt_otel_current_span->statuses))) {
+                       struct otelc_value extra = { .u_type = OTELC_VALUE_INT32, .u.value_int32 = status[i].code };
+
+                       retval = flt_otel_parse_cfg_sample(file, line, args, 2, 0, &extra, &(flt_otel_current_span->statuses), &err);
+               }
+               else {
+                       FLT_OTEL_PARSE_ERR(&err, "only one status per event is allowed");
+               }
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_INJECT) {
+               /*
+                * Automatic context name generation can be specified here if
+                * the contents of the FLT_OTEL_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_otel_current_span->ctx_id != NULL)
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : only one context per span is allowed", args[1]);
+               else if (!FLT_OTEL_PARSE_KEYWORD(1, FLT_OTEL_PARSE_CTX_AUTONAME))
+                       retval = flt_otel_parse_strdup(&(flt_otel_current_span->ctx_id), &(flt_otel_current_span->ctx_id_len), args[1], &err, args[0]);
+               else if (flt_otel_current_scope->event != FLT_OTEL_EVENT__NONE)
+                       retval = flt_otel_parse_strdup(&(flt_otel_current_span->ctx_id), &(flt_otel_current_span->ctx_id_len), flt_otel_event_data[flt_otel_current_scope->event].name, &err, args[0]);
+               else {
+                       const char *ch;
+
+                       ch = invalid_prefix_char(flt_otel_current_span->id);
+                       if (ch == NULL)
+                               retval = flt_otel_parse_strdup(&(flt_otel_current_span->ctx_id), &(flt_otel_current_span->ctx_id_len), flt_otel_current_span->id, &err, args[0]);
+                       else
+                               FLT_OTEL_PARSE_ERR(&err, "'%s' : character '%c' is not permitted in the context name", flt_otel_current_span->id, *ch);
+               }
+
+               if (flt_otel_current_span->ctx_id != NULL) {
+                       /*
+                        * 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_OTEL_ARG_ISVALID(2)) {
+                               retval |= flt_otel_parse_cfg_scope_ctx(args, 2, &err);
+                               if (!(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(3))
+                                       retval |= flt_otel_parse_cfg_scope_ctx(args, 3, &err);
+                       } else {
+                               flt_otel_current_span->ctx_flags = FLT_OTEL_CTX_USE_HEADERS;
+                       }
+               }
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_EXTRACT) {
+               struct flt_otel_conf_context *conf_ctx;
+
+               /*
+                * Here is checked the context storage type; which, if
+                * not explicitly specified, is set to HTTP headers.
+                */
+               conf_ctx = flt_otel_conf_context_init(args[1], line, &(flt_otel_current_scope->contexts), &err);
+               if (conf_ctx == NULL)
+                       retval |= ERR_ABORT | ERR_ALERT;
+               else if (!FLT_OTEL_ARG_ISVALID(2))
+                       conf_ctx->flags = FLT_OTEL_CTX_USE_HEADERS;
+               else if (FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_PARSE_CTX_USE_HEADERS))
+                       conf_ctx->flags = FLT_OTEL_CTX_USE_HEADERS;
+               else
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : invalid context storage type", args[2]);
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_FINISH) {
+               retval = flt_otel_parse_cfg_str(file, line, args, &(flt_otel_current_scope->spans_to_finish), &err);
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ACL) {
+               if (FLT_OTEL_PARSE_KEYWORD(1, "or"))
+                       FLT_OTEL_PARSE_ERR(&err, "'%s %s ...' : invalid ACL name", args[0], args[1]);
+               else if (parse_acl((const char **)args + 1, &(flt_otel_current_scope->acls), &err, &(flt_otel_current_config->proxy->conf.args), file, line) == NULL)
+                       retval |= ERR_ABORT | ERR_ALERT;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_IDLE_TIMEOUT) {
+               const char *res;
+               uint        timeout;
+
+               res = parse_time_err(args[1], &timeout, TIME_UNIT_MS);
+               if (res == PARSE_TIME_OVER)
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : timer overflow in argument '%s'", args[0], args[1]);
+               else if (res == PARSE_TIME_UNDER)
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : timer underflow in argument '%s'", args[0], args[1]);
+               else if (res != NULL)
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : unexpected character '%c' in '%s'", args[0], *res, args[1]);
+               else if (timeout == 0)
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : value must be greater than zero", args[0]);
+               else
+                       flt_otel_current_scope->idle_timeout = timeout;
+       }
+       else if (pdata->keyword == FLT_OTEL_PARSE_SCOPE_ON_EVENT) {
+               /* Scope can only have one event defined. */
+               if (flt_otel_current_scope->event != FLT_OTEL_EVENT__NONE) {
+                       FLT_OTEL_PARSE_ERR(&err, "'%s' : event already set", flt_otel_current_scope->id);
+               } else {
+                       /* Check the event name. */
+                       for (i = 0; i < OTELC_TABLESIZE(flt_otel_event_data); i++)
+                               if (FLT_OTEL_PARSE_KEYWORD(1, flt_otel_event_data[i].name)) {
+                                       flt_otel_current_scope->event = i;
+
+                                       break;
+                               }
+
+                       /*
+                        * The event can have some condition defined and this
+                        * is checked here.
+                        */
+                       if (flt_otel_current_scope->event == FLT_OTEL_EVENT__NONE) {
+                               FLT_OTEL_PARSE_ERR(&err, "'%s' : unknown event", args[1]);
+                       }
+                       else if (!FLT_OTEL_ARG_ISVALID(2)) {
+                               /* Do nothing. */
+                       }
+                       else if (FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_CONDITION_IF) || FLT_OTEL_PARSE_KEYWORD(2, FLT_OTEL_CONDITION_UNLESS)) {
+                               /*
+                                * We will first try to build ACL condition using
+                                * local settings and then if that fails, using
+                                * global settings (from instrumentation block).
+                                * If it also fails, then try to use ACL defined
+                                * in the HAProxy configuration.
+                                */
+                               if (flt_otel_current_config->instr == NULL) {
+                                       FLT_OTEL_PARSE_ERR(&err, "'%s' : instrumentation not defined", args[1]);
+                               } else {
+                                       flt_otel_current_scope->cond = flt_otel_parse_acl(file, line, flt_otel_current_config->proxy, (const char **)args + 2, &err, &(flt_otel_current_scope->acls), &(flt_otel_current_config->instr->acls), &(flt_otel_current_config->proxy->acl), NULL);
+                                       if (flt_otel_current_scope->cond == NULL)
+                                               retval |= ERR_ABORT | ERR_ALERT;
+                               }
+                       }
+                       else {
+                               FLT_OTEL_PARSE_ERR(&err, "'%s' : expects either 'if' or 'unless' followed by a condition but found '%s'", args[1], args[2]);
+                       }
+
+                       if (!(retval & ERR_CODE))
+                               OTELC_DBG(DEBUG, "event '%s'", args[1]);
+               }
+       }
+
+       FLT_OTEL_PARSE_IFERR_ALERT();
+
+       if ((retval & ERR_CODE) && (flt_otel_current_scope != NULL)) {
+               flt_otel_conf_scope_free(&flt_otel_current_scope);
+
+               flt_otel_current_span = NULL;
+       }
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_post_parse_cfg_scope - otel-scope post-parse check
+ *
+ * SYNOPSIS
+ *   static int flt_otel_post_parse_cfg_scope(void)
+ *
+ * ARGUMENTS
+ *   This function takes no arguments.
+ *
+ * DESCRIPTION
+ *   Post-parse callback for the otel-scope section.  Verifies that HTTP header
+ *   injection is only used on events that support it.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_post_parse_cfg_scope(void)
+{
+       struct flt_otel_conf_span *conf_span;
+       int                      retval = ERR_NONE;
+
+       OTELC_FUNC("");
+
+       if (flt_otel_current_scope == NULL)
+               OTELC_RETURN_INT(retval);
+
+       /* If span context inject is used, check that this is possible. */
+       list_for_each_entry(conf_span, &(flt_otel_current_scope->spans), list)
+               if ((conf_span->ctx_id != NULL) && (conf_span->ctx_flags & FLT_OTEL_CTX_USE_HEADERS))
+                       if (!flt_otel_event_data[flt_otel_current_scope->event].flag_http_inject)
+                               FLT_OTEL_POST_PARSE_ALERT("inject '%s' : cannot use on this event", conf_span->cfg_line, conf_span->ctx_id);
+
+       /* Validate idle-timeout / on-idle-timeout consistency. */
+       if (flt_otel_current_scope->idle_timeout == 0) {
+               if (flt_otel_current_scope->event == FLT_OTEL_EVENT__IDLE_TIMEOUT)
+                       FLT_OTEL_POST_PARSE_ALERT("'%s' : 'idle-timeout' is required for event 'on-idle-timeout'", flt_otel_current_scope->cfg_line, flt_otel_current_scope->id);
+       }
+       else if (flt_otel_current_scope->event != FLT_OTEL_EVENT__IDLE_TIMEOUT) {
+               FLT_OTEL_POST_PARSE_ALERT("'%s' : 'idle-timeout' can only be used with event 'on-idle-timeout'", flt_otel_current_scope->cfg_line, flt_otel_current_scope->id);
+       }
+
+       if (retval & ERR_CODE)
+               flt_otel_conf_scope_free(&flt_otel_current_scope);
+
+       flt_otel_current_scope = NULL;
+       flt_otel_current_span  = NULL;
+
+       OTELC_RETURN_INT(retval);
+}
+
+
+/***
+ * NAME
+ *   flt_otel_parse_cfg - OTel configuration file parser
+ *
+ * SYNOPSIS
+ *   static int flt_otel_parse_cfg(struct flt_otel_conf *conf, const char *flt_name, char **err)
+ *
+ * ARGUMENTS
+ *   conf     - pointer to the filter configuration structure
+ *   flt_name - filter name for error reporting
+ *   err      - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses the OTel filter configuration file.  Backs up the current HAProxy
+ *   section parsers, registers temporary otel-instrumentation, otel-group, and
+ *   otel-scope section parsers, loads and parses the file, then restores the
+ *   original sections.
+ *
+ * RETURN VALUE
+ *   Returns ERR_NONE (== 0) in case of success,
+ *   or a combination of ERR_* flags if an error is encountered.
+ */
+static int flt_otel_parse_cfg(struct flt_otel_conf *conf, const char *flt_name, char **err)
+{
+       struct list    backup_sections;
+       struct cfgfile cfg_file;
+       int            retval = ERR_ABORT | ERR_ALERT;
+
+       OTELC_FUNC("%p, \"%s\", %p:%p", conf, OTELC_STR_ARG(flt_name), OTELC_DPTR_ARGS(err));
+
+       flt_otel_current_config = conf;
+
+       /* Backup sections. */
+       LIST_INIT(&backup_sections);
+       cfg_backup_sections(&backup_sections);
+
+       /* Register new OTEL sections and parse the OTEL filter configuration file. */
+       if (!cfg_register_section(FLT_OTEL_PARSE_SECTION_INSTR_ID, flt_otel_parse_cfg_instr, flt_otel_post_parse_cfg_instr))
+               /* Do nothing. */;
+       else if (!cfg_register_section(FLT_OTEL_PARSE_SECTION_GROUP_ID, flt_otel_parse_cfg_group, flt_otel_post_parse_cfg_group))
+               /* Do nothing. */;
+       else if (!cfg_register_section(FLT_OTEL_PARSE_SECTION_SCOPE_ID, flt_otel_parse_cfg_scope, flt_otel_post_parse_cfg_scope))
+               /* Do nothing. */;
+       else if (access(conf->cfg_file, R_OK) == -1)
+               FLT_OTEL_PARSE_ERR(err, "'%s' : %s", conf->cfg_file, strerror(errno));
+       else {
+               struct list saved_args = LIST_HEAD_INIT(saved_args);
+
+               /*
+                * Sample fetch arguments queued during parsing are normally
+                * resolved by smp_resolve_args() in the proxy
+                * post-configuration phase.  That call uses the proxy's own
+                * capabilities, so backend-only fetches like be_conn would
+                * fail when the filter is attached to a frontend.
+                *
+                * The OTel filter spans both request and response channels,
+                * so its sample fetches must be resolved with full FE+BE
+                * capabilities.  To achieve this the proxy's arg list is saved
+                * and replaced with a fresh one before parsing.  The OTel
+                * config parser adds only ARGC_OTEL entries to the new list.
+                * After parsing, those entries are moved to conf->smp_args and
+                * resolved later in flt_otel_check(), which runs after all
+                * configuration sections have been parsed so that backends and
+                * servers are available.
+                */
+               LIST_SPLICE(&saved_args, &(conf->proxy->conf.args.list));
+               LIST_INIT(&(conf->proxy->conf.args.list));
+
+               (void)memset(&cfg_file, 0, sizeof(cfg_file));
+               cfg_file.filename = conf->cfg_file;
+               cfg_file.size     = load_cfg_in_mem(cfg_file.filename, &(cfg_file.content));
+               if (cfg_file.size >= 0)
+                       retval = parse_cfg(&cfg_file);
+               ha_free(&(cfg_file.content));
+
+               /* Stash OTEL args for deferred resolution. */
+               LIST_SPLICE(&(conf->smp_args), &(conf->proxy->conf.args.list));
+               LIST_INIT(&(conf->proxy->conf.args.list));
+
+               /* Restore the original arg list unchanged. */
+               LIST_SPLICE(&(conf->proxy->conf.args.list), &saved_args);
+       }
+
+       /* Unregister OTEL sections and restore previous sections. */
+       cfg_unregister_sections();
+       cfg_restore_sections(&backup_sections);
+
+       flt_otel_current_config = NULL;
+
+       OTELC_RETURN_INT(retval);
+}
+
 
 /***
  * NAME
@@ -36,22 +1291,81 @@ static struct otelc_dbg_mem      dbg_mem;
  */
 static int flt_otel_parse(char **args, int *cur_arg, struct proxy *px, struct flt_conf *fconf, char **err, void *private)
 {
-       int retval = ERR_NONE;
+       struct flt_otel_conf *conf = NULL;
+       int                   pos, retval = ERR_NONE;
 
        OTELC_FUNC("%p, %p, %p, %p, %p:%p, %p", args, cur_arg, px, fconf, OTELC_DPTR_ARGS(err), private);
 
+       if (!(global.tune.options & GTUNE_INSECURE_FORK)) {
+               FLT_OTEL_PARSE_ERR(err, "The 'insecure-fork-wanted' option must be enabled in the HAProxy configuration because the OpenTelemetry filter cannot work properly if the creation of threads is forbidden.");
+
+               OTELC_RETURN_INT(retval);
+       }
+
        OTELC_DBG_IFDEF(otelc_dbg_level = FLT_OTEL_DEBUG_LEVEL, );
 
 #ifdef OTELC_DBG_MEM
        /* Initialize the debug memory tracker before the first allocation. */
        FLT_OTEL_RUN_ONCE(
-               if (otelc_dbg_mem_init(&dbg_mem, dbg_mem_data, OTELC_TABLESIZE(dbg_mem_data)) == -1)
+               if (otelc_dbg_mem_init(&dbg_mem, dbg_mem_data, OTELC_TABLESIZE(dbg_mem_data)) == -1) {
+                       FLT_OTEL_PARSE_ERR(err, "cannot initialize memory debugger");
+
                        OTELC_RETURN_INT(retval);
+               }
        );
 #endif
 
        FLT_OTEL_ARGS_DUMP();
 
+       conf = flt_otel_conf_init(px);
+       if (conf == NULL) {
+               FLT_OTEL_PARSE_ERR(err, "'%s' : out of memory", args[*cur_arg]);
+
+               OTELC_RETURN_INT(retval);
+       }
+
+       /* Process filter option key-value pairs. */
+       for (pos = *cur_arg + 1; !(retval & ERR_CODE) && FLT_OTEL_ARG_ISVALID(pos); pos++) {
+               OTELC_DBG(DEBUG, "args[%d:2]: { '%s' '%s' }", pos, args[pos], args[pos + 1]);
+
+               if (FLT_OTEL_PARSE_KEYWORD(pos, FLT_OTEL_OPT_FILTER_ID)) {
+                       retval = flt_otel_parse_keyword(&(conf->id), args, *cur_arg, pos, err, "name");
+                       pos++;
+               }
+               else if (FLT_OTEL_PARSE_KEYWORD(pos, FLT_OTEL_OPT_CONFIG)) {
+                       retval = flt_otel_parse_keyword(&(conf->cfg_file), args, *cur_arg, pos, err, "configuration file");
+                       if (!(retval & ERR_CODE))
+                               retval = flt_otel_parse_cfg(conf, args[*cur_arg], err);
+                       pos++;
+               }
+               else {
+                       FLT_OTEL_PARSE_ERR(err, "'%s' : unknown keyword '%s'", args[*cur_arg], args[pos]);
+               }
+       }
+
+       /* If the OpenTelemetry filter ID is not set, use default name. */
+       if (!(retval & ERR_CODE) && (conf->id == NULL)) {
+               ha_warning("parsing : " FLT_OTEL_FMT_TYPE FLT_OTEL_FMT_NAME "'no filter id set, using default id '%s'\n", FLT_OTEL_OPT_FILTER_ID_DEFAULT);
+
+               retval = flt_otel_parse_strdup(&(conf->id), NULL, FLT_OTEL_OPT_FILTER_ID_DEFAULT, err, args[*cur_arg]);
+       }
+
+       if (!(retval & ERR_CODE) && (conf->cfg_file == NULL))
+               FLT_OTEL_PARSE_ERR(err, "'%s' : no configuration file specified", args[*cur_arg]);
+
+       if (retval & ERR_CODE) {
+               flt_otel_conf_free(&conf);
+       } else {
+               fconf->id   = otel_flt_id;
+               fconf->ops  = &flt_otel_ops;
+               fconf->conf = conf;
+
+               *cur_arg = pos;
+
+               OTELC_DBG(DEBUG, "filter set: id '%s', config '%s'", conf->id, conf->cfg_file);
+               FLT_OTEL_DBG_CONF("- conf ", (typeof(conf))fconf->conf);
+       }
+
        OTELC_RETURN_INT(retval);
 }
 
index 6be6cd3688645a6f1757a4a83514e610f2891ea1..a951efb4a62f3d24f79cfc34f1a4d9a6946d7cdf 100644 (file)
@@ -174,6 +174,96 @@ int flt_otel_args_concat(const char **args, int idx, int n, char **str)
 }
 
 
+/***
+ * NAME
+ *   flt_otel_strtod - string to double conversion with range check
+ *
+ * SYNOPSIS
+ *   bool flt_otel_strtod(const char *nptr, double *value, double limit_min, double limit_max, char **err)
+ *
+ * ARGUMENTS
+ *   nptr      - string to parse
+ *   value     - pointer to store the parsed double result
+ *   limit_min - minimum allowed value
+ *   limit_max - maximum allowed value
+ *   err       - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses the string <nptr> as a double-precision floating-point number.
+ *   Validates that the entire string is consumed and that the result falls
+ *   within the range [<limit_min>, <limit_max>].  On parse error or range
+ *   violation, an error message is stored via <err>.
+ *
+ * RETURN VALUE
+ *   Returns true on success, false on failure.
+ */
+bool flt_otel_strtod(const char *nptr, double *value, double limit_min, double limit_max, char **err)
+{
+       char *endptr = NULL;
+       bool  retval = false;
+
+       if (value == NULL)
+               return retval;
+
+       errno = 0;
+
+       *value = strtod(nptr, &endptr);
+       if ((errno != 0) || OTELC_STR_IS_VALID(endptr))
+               FLT_OTEL_ERR("'%s' : invalid value", nptr);
+       else if (!OTELC_IN_RANGE(*value, limit_min, limit_max))
+               FLT_OTEL_ERR("'%s' : value out of range [%.2f, %.2f]", nptr, limit_min, limit_max);
+       else
+               retval = true;
+
+       return retval;
+}
+
+
+/***
+ * NAME
+ *   flt_otel_strtoll - string to int64_t conversion with range check
+ *
+ * SYNOPSIS
+ *   bool flt_otel_strtoll(const char *nptr, int64_t *value, int64_t limit_min, int64_t limit_max, char **err)
+ *
+ * ARGUMENTS
+ *   nptr      - string to parse
+ *   value     - pointer to store the parsed int64_t result
+ *   limit_min - minimum allowed value
+ *   limit_max - maximum allowed value
+ *   err       - indirect pointer to error message string
+ *
+ * DESCRIPTION
+ *   Parses the string <nptr> as a 64-bit integer using base auto-detection.
+ *   Validates that the entire string is consumed and that the result falls
+ *   within the range [<limit_min>, <limit_max>].  On parse error or range
+ *   violation, an error message is stored via <err>.
+ *
+ * RETURN VALUE
+ *   Returns true on success, false on failure.
+ */
+bool flt_otel_strtoll(const char *nptr, int64_t *value, int64_t limit_min, int64_t limit_max, char **err)
+{
+       char *endptr = NULL;
+       bool  retval = false;
+
+       if (value == NULL)
+               return retval;
+
+       errno = 0;
+
+       *value = strtoll(nptr, &endptr, 0);
+       if ((errno != 0) || OTELC_STR_IS_VALID(endptr))
+               FLT_OTEL_ERR("'%s' : invalid value", nptr);
+       else if (!OTELC_IN_RANGE(*value, limit_min, limit_max))
+               FLT_OTEL_ERR("'%s' : value out of range [%" PRId64 ", %" PRId64 "]", nptr, limit_min, limit_max);
+       else
+               retval = true;
+
+       return retval;
+}
+
+
 /***
  * NAME
  *   flt_otel_sample_to_str - sample data to string conversion