summaryrefslogtreecommitdiffstats
path: root/src/flt_trace.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:18:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:18:05 +0000
commitb46aad6df449445a9fc4aa7b32bd40005438e3f7 (patch)
tree751aa858ca01f35de800164516b298887382919d /src/flt_trace.c
parentInitial commit. (diff)
downloadhaproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.tar.xz
haproxy-b46aad6df449445a9fc4aa7b32bd40005438e3f7.zip
Adding upstream version 2.9.5.upstream/2.9.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/flt_trace.c')
-rw-r--r--src/flt_trace.c675
1 files changed, 675 insertions, 0 deletions
diff --git a/src/flt_trace.c b/src/flt_trace.c
new file mode 100644
index 0000000..bbadfe2
--- /dev/null
+++ b/src/flt_trace.c
@@ -0,0 +1,675 @@
+/*
+ * Stream filters related variables and functions.
+ *
+ * Copyright (C) 2015 Qualys Inc., Christopher Faulet <cfaulet@qualys.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <ctype.h>
+
+#include <haproxy/api.h>
+#include <haproxy/channel-t.h>
+#include <haproxy/errors.h>
+#include <haproxy/filters.h>
+#include <haproxy/global.h>
+#include <haproxy/http_ana-t.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/htx.h>
+#include <haproxy/proxy-t.h>
+#include <haproxy/stream.h>
+#include <haproxy/time.h>
+#include <haproxy/tools.h>
+
+const char *trace_flt_id = "trace filter";
+
+struct flt_ops trace_ops;
+
+#define TRACE_F_QUIET 0x00000001
+#define TRACE_F_RAND_FWD 0x00000002
+#define TRACE_F_HEXDUMP 0x00000004
+
+struct trace_config {
+ struct proxy *proxy;
+ char *name;
+ unsigned int flags;
+};
+
+#define FLT_TRACE(conf, fmt, ...) \
+ do { \
+ if (!(conf->flags & TRACE_F_QUIET)) \
+ fprintf(stderr, "%d.%06d [%-20s] " fmt "\n", \
+ (int)date.tv_sec, (int)date.tv_usec, (conf)->name,\
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define FLT_STRM_TRACE(conf, strm, fmt, ...) \
+ do { \
+ if (!(conf->flags & TRACE_F_QUIET)) \
+ fprintf(stderr, "%d.%06d [%-20s] [strm %p(%x) 0x%08x 0x%08x] " fmt "\n", \
+ (int)date.tv_sec, (int)date.tv_usec, (conf)->name, \
+ strm, (strm ? ((struct stream *)strm)->uniq_id : ~0U), \
+ (strm ? strm->req.analysers : 0), (strm ? strm->res.analysers : 0), \
+ ##__VA_ARGS__); \
+ } while (0)
+
+
+static const char *
+channel_label(const struct channel *chn)
+{
+ return (chn->flags & CF_ISRESP) ? "RESPONSE" : "REQUEST";
+}
+
+static const char *
+proxy_mode(const struct stream *s)
+{
+ struct proxy *px = (s->flags & SF_BE_ASSIGNED ? s->be : strm_fe(s));
+
+ return ((px->mode == PR_MODE_HTTP) ? "HTTP" : "TCP");
+}
+
+static const char *
+stream_pos(const struct stream *s)
+{
+ return (s->flags & SF_BE_ASSIGNED) ? "backend" : "frontend";
+}
+
+static const char *
+filter_type(const struct filter *f)
+{
+ return (f->flags & FLT_FL_IS_BACKEND_FILTER) ? "backend" : "frontend";
+}
+
+static void
+trace_hexdump(struct ist ist)
+{
+ int i, j, padding;
+
+ padding = ((ist.len % 16) ? (16 - ist.len % 16) : 0);
+ for (i = 0; i < ist.len + padding; i++) {
+ if (!(i % 16))
+ fprintf(stderr, "\t0x%06x: ", i);
+ else if (!(i % 8))
+ fprintf(stderr, " ");
+
+ if (i < ist.len)
+ fprintf(stderr, "%02x ", (unsigned char)*(ist.ptr+i));
+ else
+ fprintf(stderr, " ");
+
+ /* print ASCII dump */
+ if (i % 16 == 15) {
+ fprintf(stderr, " |");
+ for(j = i - 15; j <= i && j < ist.len; j++)
+ fprintf(stderr, "%c", (isprint((unsigned char)*(ist.ptr+j)) ? *(ist.ptr+j) : '.'));
+ fprintf(stderr, "|\n");
+ }
+ }
+}
+
+static void
+trace_raw_hexdump(struct buffer *buf, unsigned int offset, unsigned int len)
+{
+ unsigned char p[len];
+ int block1, block2;
+
+ block1 = len;
+ if (block1 > b_contig_data(buf, offset))
+ block1 = b_contig_data(buf, offset);
+ block2 = len - block1;
+
+ memcpy(p, b_peek(buf, offset), block1);
+ memcpy(p+block1, b_orig(buf), block2);
+ trace_hexdump(ist2(p, len));
+}
+
+static void
+trace_htx_hexdump(struct htx *htx, unsigned int offset, unsigned int len)
+{
+ struct htx_blk *blk;
+
+ for (blk = htx_get_first_blk(htx); blk && len; blk = htx_get_next_blk(htx, blk)) {
+ enum htx_blk_type type = htx_get_blk_type(blk);
+ uint32_t sz = htx_get_blksz(blk);
+ struct ist v;
+
+ if (offset >= sz) {
+ offset -= sz;
+ continue;
+ }
+
+ v = htx_get_blk_value(htx, blk);
+ v = istadv(v, offset);
+ offset = 0;
+
+ v = isttrim(v, len);
+ len -= v.len;
+ if (type == HTX_BLK_DATA)
+ trace_hexdump(v);
+ }
+}
+
+static unsigned int
+trace_get_htx_datalen(struct htx *htx, unsigned int offset, unsigned int len)
+{
+ struct htx_blk *blk;
+ struct htx_ret htxret = htx_find_offset(htx, offset);
+ uint32_t data = 0;
+
+ blk = htxret.blk;
+ if (blk && htxret.ret && htx_get_blk_type(blk) == HTX_BLK_DATA) {
+ data += htxret.ret;
+ blk = htx_get_next_blk(htx, blk);
+ }
+ while (blk) {
+ if (htx_get_blk_type(blk) == HTX_BLK_UNUSED)
+ goto next;
+ else if (htx_get_blk_type(blk) != HTX_BLK_DATA)
+ break;
+ data += htx_get_blksz(blk);
+ next:
+ blk = htx_get_next_blk(htx, blk);
+ }
+ return data;
+}
+
+/***************************************************************************
+ * Hooks that manage the filter lifecycle (init/check/deinit)
+ **************************************************************************/
+/* Initialize the filter. Returns -1 on error, else 0. */
+static int
+trace_init(struct proxy *px, struct flt_conf *fconf)
+{
+ struct trace_config *conf = fconf->conf;
+
+ if (conf->name)
+ memprintf(&conf->name, "%s/%s", conf->name, px->id);
+ else
+ memprintf(&conf->name, "TRACE/%s", px->id);
+
+ fconf->flags |= FLT_CFG_FL_HTX;
+ fconf->conf = conf;
+
+ FLT_TRACE(conf, "filter initialized [quiet=%s - fwd random=%s - hexdump=%s]",
+ ((conf->flags & TRACE_F_QUIET) ? "true" : "false"),
+ ((conf->flags & TRACE_F_RAND_FWD) ? "true" : "false"),
+ ((conf->flags & TRACE_F_HEXDUMP) ? "true" : "false"));
+ return 0;
+}
+
+/* Free resources allocated by the trace filter. */
+static void
+trace_deinit(struct proxy *px, struct flt_conf *fconf)
+{
+ struct trace_config *conf = fconf->conf;
+
+ if (conf) {
+ FLT_TRACE(conf, "filter deinitialized");
+ free(conf->name);
+ free(conf);
+ }
+ fconf->conf = NULL;
+}
+
+/* Check configuration of a trace filter for a specified proxy.
+ * Return 1 on error, else 0. */
+static int
+trace_check(struct proxy *px, struct flt_conf *fconf)
+{
+ return 0;
+}
+
+/* Initialize the filter for each thread. Return -1 on error, else 0. */
+static int
+trace_init_per_thread(struct proxy *px, struct flt_conf *fconf)
+{
+ struct trace_config *conf = fconf->conf;
+
+ FLT_TRACE(conf, "filter initialized for thread tid %u", tid);
+ return 0;
+}
+
+/* Free resources allocate by the trace filter for each thread. */
+static void
+trace_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
+{
+ struct trace_config *conf = fconf->conf;
+
+ if (conf)
+ FLT_TRACE(conf, "filter deinitialized for thread tid %u", tid);
+}
+
+/**************************************************************************
+ * Hooks to handle start/stop of streams
+ *************************************************************************/
+/* Called when a filter instance is created and attach to a stream */
+static int
+trace_attach(struct stream *s, struct filter *filter)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: filter-type=%s",
+ __FUNCTION__, filter_type(filter));
+
+ return 1;
+}
+
+/* Called when a filter instance is detach from a stream, just before its
+ * destruction */
+static void
+trace_detach(struct stream *s, struct filter *filter)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: filter-type=%s",
+ __FUNCTION__, filter_type(filter));
+}
+
+/* Called when a stream is created */
+static int
+trace_stream_start(struct stream *s, struct filter *filter)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s",
+ __FUNCTION__);
+ return 0;
+}
+
+
+/* Called when a backend is set for a stream */
+static int
+trace_stream_set_backend(struct stream *s, struct filter *filter,
+ struct proxy *be)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: backend=%s",
+ __FUNCTION__, be->id);
+ return 0;
+}
+
+/* Called when a stream is destroyed */
+static void
+trace_stream_stop(struct stream *s, struct filter *filter)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s",
+ __FUNCTION__);
+}
+
+/* Called when the stream is woken up because of an expired timer */
+static void
+trace_check_timeouts(struct stream *s, struct filter *filter)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s",
+ __FUNCTION__);
+}
+
+/**************************************************************************
+ * Hooks to handle channels activity
+ *************************************************************************/
+/* Called when analyze starts for a given channel */
+static int
+trace_chn_start_analyze(struct stream *s, struct filter *filter,
+ struct channel *chn)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s)",
+ __FUNCTION__,
+ channel_label(chn), proxy_mode(s), stream_pos(s));
+ filter->pre_analyzers |= (AN_REQ_ALL | AN_RES_ALL);
+ filter->post_analyzers |= (AN_REQ_ALL | AN_RES_ALL);
+ register_data_filter(s, chn, filter);
+ return 1;
+}
+
+/* Called before a processing happens on a given channel */
+static int
+trace_chn_analyze(struct stream *s, struct filter *filter,
+ struct channel *chn, unsigned an_bit)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+ char *ana;
+
+ switch (an_bit) {
+ case AN_REQ_INSPECT_FE:
+ ana = "AN_REQ_INSPECT_FE";
+ break;
+ case AN_REQ_WAIT_HTTP:
+ ana = "AN_REQ_WAIT_HTTP";
+ break;
+ case AN_REQ_HTTP_BODY:
+ ana = "AN_REQ_HTTP_BODY";
+ break;
+ case AN_REQ_HTTP_PROCESS_FE:
+ ana = "AN_REQ_HTTP_PROCESS_FE";
+ break;
+ case AN_REQ_SWITCHING_RULES:
+ ana = "AN_REQ_SWITCHING_RULES";
+ break;
+ case AN_REQ_INSPECT_BE:
+ ana = "AN_REQ_INSPECT_BE";
+ break;
+ case AN_REQ_HTTP_PROCESS_BE:
+ ana = "AN_REQ_HTTP_PROCESS_BE";
+ break;
+ case AN_REQ_SRV_RULES:
+ ana = "AN_REQ_SRV_RULES";
+ break;
+ case AN_REQ_HTTP_INNER:
+ ana = "AN_REQ_HTTP_INNER";
+ break;
+ case AN_REQ_HTTP_TARPIT:
+ ana = "AN_REQ_HTTP_TARPIT";
+ break;
+ case AN_REQ_STICKING_RULES:
+ ana = "AN_REQ_STICKING_RULES";
+ break;
+ case AN_REQ_PRST_RDP_COOKIE:
+ ana = "AN_REQ_PRST_RDP_COOKIE";
+ break;
+ case AN_REQ_HTTP_XFER_BODY:
+ ana = "AN_REQ_HTTP_XFER_BODY";
+ break;
+ case AN_RES_INSPECT:
+ ana = "AN_RES_INSPECT";
+ break;
+ case AN_RES_WAIT_HTTP:
+ ana = "AN_RES_WAIT_HTTP";
+ break;
+ case AN_RES_HTTP_PROCESS_FE: // AN_RES_HTTP_PROCESS_BE
+ ana = "AN_RES_HTTP_PROCESS_FE/BE";
+ break;
+ case AN_RES_STORE_RULES:
+ ana = "AN_RES_STORE_RULES";
+ break;
+ case AN_RES_HTTP_XFER_BODY:
+ ana = "AN_RES_HTTP_XFER_BODY";
+ break;
+ default:
+ ana = "unknown";
+ }
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s) - "
+ "analyzer=%s - step=%s",
+ __FUNCTION__,
+ channel_label(chn), proxy_mode(s), stream_pos(s),
+ ana, ((chn->analysers & an_bit) ? "PRE" : "POST"));
+ return 1;
+}
+
+/* Called when analyze ends for a given channel */
+static int
+trace_chn_end_analyze(struct stream *s, struct filter *filter,
+ struct channel *chn)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s)",
+ __FUNCTION__,
+ channel_label(chn), proxy_mode(s), stream_pos(s));
+ return 1;
+}
+
+/**************************************************************************
+ * Hooks to filter HTTP messages
+ *************************************************************************/
+static int
+trace_http_headers(struct stream *s, struct filter *filter,
+ struct http_msg *msg)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+ struct htx *htx = htxbuf(&msg->chn->buf);
+ struct htx_sl *sl = http_get_stline(htx);
+ int32_t pos;
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s)\t%.*s %.*s %.*s",
+ __FUNCTION__,
+ channel_label(msg->chn), proxy_mode(s), stream_pos(s),
+ HTX_SL_P1_LEN(sl), HTX_SL_P1_PTR(sl),
+ HTX_SL_P2_LEN(sl), HTX_SL_P2_PTR(sl),
+ HTX_SL_P3_LEN(sl), HTX_SL_P3_PTR(sl));
+
+ for (pos = htx_get_first(htx); pos != -1; pos = htx_get_next(htx, pos)) {
+ struct htx_blk *blk = htx_get_blk(htx, pos);
+ enum htx_blk_type type = htx_get_blk_type(blk);
+ struct ist n, v;
+
+ if (type == HTX_BLK_EOH)
+ break;
+ if (type != HTX_BLK_HDR)
+ continue;
+
+ n = htx_get_blk_name(htx, blk);
+ v = htx_get_blk_value(htx, blk);
+ FLT_STRM_TRACE(conf, s, "\t%.*s: %.*s",
+ (int)n.len, n.ptr, (int)v.len, v.ptr);
+ }
+ return 1;
+}
+
+static int
+trace_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg,
+ unsigned int offset, unsigned int len)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+ int ret = len;
+
+ if (ret && (conf->flags & TRACE_F_RAND_FWD)) {
+ unsigned int data = trace_get_htx_datalen(htxbuf(&msg->chn->buf), offset, len);
+
+ if (data) {
+ ret = ha_random() % (ret+1);
+ if (!ret || ret >= data)
+ ret = len;
+ }
+ }
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s) - "
+ "offset=%u - len=%u - forward=%d",
+ __FUNCTION__,
+ channel_label(msg->chn), proxy_mode(s), stream_pos(s),
+ offset, len, ret);
+
+ if (conf->flags & TRACE_F_HEXDUMP)
+ trace_htx_hexdump(htxbuf(&msg->chn->buf), offset, ret);
+
+ if (ret != len)
+ task_wakeup(s->task, TASK_WOKEN_MSG);
+ return ret;
+}
+
+static int
+trace_http_end(struct stream *s, struct filter *filter,
+ struct http_msg *msg)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s)",
+ __FUNCTION__,
+ channel_label(msg->chn), proxy_mode(s), stream_pos(s));
+ return 1;
+}
+
+static void
+trace_http_reset(struct stream *s, struct filter *filter,
+ struct http_msg *msg)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s)",
+ __FUNCTION__,
+ channel_label(msg->chn), proxy_mode(s), stream_pos(s));
+}
+
+static void
+trace_http_reply(struct stream *s, struct filter *filter, short status,
+ const struct buffer *msg)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s)",
+ __FUNCTION__, "-", proxy_mode(s), stream_pos(s));
+}
+
+/**************************************************************************
+ * Hooks to filter TCP data
+ *************************************************************************/
+static int
+trace_tcp_payload(struct stream *s, struct filter *filter, struct channel *chn,
+ unsigned int offset, unsigned int len)
+{
+ struct trace_config *conf = FLT_CONF(filter);
+ int ret = len;
+
+ if (s->flags & SF_HTX) {
+ if (ret && (conf->flags & TRACE_F_RAND_FWD)) {
+ unsigned int data = trace_get_htx_datalen(htxbuf(&chn->buf), offset, len);
+
+ if (data) {
+ ret = ha_random() % (ret+1);
+ if (!ret || ret >= data)
+ ret = len;
+ }
+ }
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s) - "
+ "offset=%u - len=%u - forward=%d",
+ __FUNCTION__,
+ channel_label(chn), proxy_mode(s), stream_pos(s),
+ offset, len, ret);
+
+ if (conf->flags & TRACE_F_HEXDUMP)
+ trace_htx_hexdump(htxbuf(&chn->buf), offset, ret);
+ }
+ else {
+
+ if (ret && (conf->flags & TRACE_F_RAND_FWD))
+ ret = ha_random() % (ret+1);
+
+ FLT_STRM_TRACE(conf, s, "%-25s: channel=%-10s - mode=%-5s (%s) - "
+ "offset=%u - len=%u - forward=%d",
+ __FUNCTION__,
+ channel_label(chn), proxy_mode(s), stream_pos(s),
+ offset, len, ret);
+
+ if (conf->flags & TRACE_F_HEXDUMP)
+ trace_raw_hexdump(&chn->buf, offset, ret);
+ }
+
+ if (ret != len)
+ task_wakeup(s->task, TASK_WOKEN_MSG);
+ return ret;
+}
+/********************************************************************
+ * Functions that manage the filter initialization
+ ********************************************************************/
+struct flt_ops trace_ops = {
+ /* Manage trace filter, called for each filter declaration */
+ .init = trace_init,
+ .deinit = trace_deinit,
+ .check = trace_check,
+ .init_per_thread = trace_init_per_thread,
+ .deinit_per_thread = trace_deinit_per_thread,
+
+ /* Handle start/stop of streams */
+ .attach = trace_attach,
+ .detach = trace_detach,
+ .stream_start = trace_stream_start,
+ .stream_set_backend = trace_stream_set_backend,
+ .stream_stop = trace_stream_stop,
+ .check_timeouts = trace_check_timeouts,
+
+ /* Handle channels activity */
+ .channel_start_analyze = trace_chn_start_analyze,
+ .channel_pre_analyze = trace_chn_analyze,
+ .channel_post_analyze = trace_chn_analyze,
+ .channel_end_analyze = trace_chn_end_analyze,
+
+ /* Filter HTTP requests and responses */
+ .http_headers = trace_http_headers,
+ .http_payload = trace_http_payload,
+ .http_end = trace_http_end,
+ .http_reset = trace_http_reset,
+ .http_reply = trace_http_reply,
+
+ /* Filter TCP data */
+ .tcp_payload = trace_tcp_payload,
+};
+
+/* Return -1 on error, else 0 */
+static int
+parse_trace_flt(char **args, int *cur_arg, struct proxy *px,
+ struct flt_conf *fconf, char **err, void *private)
+{
+ struct trace_config *conf;
+ int pos = *cur_arg;
+
+ conf = calloc(1, sizeof(*conf));
+ if (!conf) {
+ memprintf(err, "%s: out of memory", args[*cur_arg]);
+ return -1;
+ }
+ conf->proxy = px;
+ conf->flags = 0;
+ if (strcmp(args[pos], "trace") == 0) {
+ pos++;
+
+ while (*args[pos]) {
+ if (strcmp(args[pos], "name") == 0) {
+ if (!*args[pos + 1]) {
+ memprintf(err, "'%s' : '%s' option without value",
+ args[*cur_arg], args[pos]);
+ goto error;
+ }
+ conf->name = strdup(args[pos + 1]);
+ if (!conf->name) {
+ memprintf(err, "%s: out of memory", args[*cur_arg]);
+ goto error;
+ }
+ pos++;
+ }
+ else if (strcmp(args[pos], "quiet") == 0)
+ conf->flags |= TRACE_F_QUIET;
+ else if (strcmp(args[pos], "random-parsing") == 0)
+ ; // ignore
+ else if (strcmp(args[pos], "random-forwarding") == 0)
+ conf->flags |= TRACE_F_RAND_FWD;
+ else if (strcmp(args[pos], "hexdump") == 0)
+ conf->flags |= TRACE_F_HEXDUMP;
+ else
+ break;
+ pos++;
+ }
+ *cur_arg = pos;
+ fconf->id = trace_flt_id;
+ fconf->ops = &trace_ops;
+ }
+
+ fconf->conf = conf;
+ return 0;
+
+ error:
+ if (conf->name)
+ free(conf->name);
+ free(conf);
+ return -1;
+}
+
+/* Declare the filter parser for "trace" keyword */
+static struct flt_kw_list flt_kws = { "TRACE", { }, {
+ { "trace", parse_trace_flt, NULL },
+ { NULL, NULL, NULL },
+ }
+};
+
+INITCALL1(STG_REGISTER, flt_register_keywords, &flt_kws);