From a2e60e30e09c9c7d881fe288074e20eb0d748219 Mon Sep 17 00:00:00 2001 From: Sebastian Harl Date: Tue, 28 Oct 2008 22:02:38 +0100 Subject: [PATCH] filter_pcre: Added a plugin to filter value lists based on PCRE. The user may specify a set of Perl-compatible regular expressions to match any component of the (host, plugin, plugin instance, type, type instance) tuple. Any of the filter flags may be used to handle a successful match. Sample plugin configuration: Host "^mail\d+$" Plugin "^tcpconns$" TypeInstance "^SYN_" Action NoWrite --- README | 10 ++ configure.in | 75 ++++++++++++ src/Makefile.am | 10 ++ src/collectd.conf.in | 11 ++ src/collectd.conf.pod | 65 ++++++++++ src/filter_pcre.c | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 500 insertions(+) create mode 100644 src/filter_pcre.c diff --git a/README b/README index b6bdb413..8ff8aeee 100644 --- a/README +++ b/README @@ -250,6 +250,12 @@ Features needed. Please read collectd-unixsock(5) for a description on how that's done. + * Filtering and rewriting values dispatched to collectd can be done by the + following plugins: + + - filter_pcre + Filter value lists based on Perl-compatible regular expressions. + * Logging is, as everything in collectd, provided by plugins. The following plugins keep up informed about what's going on: @@ -416,6 +422,10 @@ Prerequisites Used to capture packets by the `dns' plugin. + * libpcre (optional) + Used by the `filter_pcre' plugin. + + * libperl (optional) Obviously used by the `perl' plugin. The library has to be compiled with ithread support (introduced in Perl 5.6.0). diff --git a/configure.in b/configure.in index 90ac4627..9319d5fc 100644 --- a/configure.in +++ b/configure.in @@ -1641,6 +1641,78 @@ AC_DEFINE_UNQUOTED(COLLECT_LIBPCAP, [$collect_libpcap], AM_CONDITIONAL(BUILD_WITH_LIBPCAP, test "x$with_libpcap" = "xyes") # }}} +# --with-libpcre {{{ +with_pcre_config="pcre-config" +with_pcre_cflags="" +with_pcre_libs="" +AC_ARG_WITH(libpcre, [AS_HELP_STRING([--with-libpcre@<:@=PREFIX@:>@], + [Path to libpcre.])], + [ + if test "x$withval" = "xno" + then + with_libpcre="no" + else if test "x$withval" = "xyes" + then + with_libpcre="yes" + else + if test -f "$withval" && test -x "$withval" + then + with_pcre_config="$withval" + else if test -x "$withval/bin/pcre-config" + then + with_pcre_config="$withval/bin/pcre-config" + fi; fi + with_libpcre="yes" + fi; fi + ], + [ + with_libpcre="yes" + ]) + +if test "x$with_libpcre" = "xyes" +then + with_pcre_cflags=`$with_pcre_config --cflags 2>/dev/null` + pcre_config_status=$? + + if test $pcre_config_status -ne 0 + then + with_libpcre="no ($with_pcre_config failed)" + else + SAVE_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $with_pcre_cflags" + + AC_CHECK_HEADERS(pcre.h, [], [with_libpcre="no (pcre.h not found)"], []) + + CPPFLAGS="$SAVE_CPPFLAGS" + fi +fi + +if test "x$with_libpcre" = "xyes" +then + with_pcre_libs=`$with_pcre_config --libs 2>/dev/null` + pcre_config_status=$? + + if test $pcre_config_status -ne 0 + then + with_libpcre="no ($with_pcre_config failed)" + else + AC_CHECK_LIB(pcre, pcre_compile, + [with_libpcre="yes"], + [with_libpcre="no (symbol 'pcre_compile' not found)"], + [$with_pcre_libs]) + fi +fi + +if test "x$with_libpcre" = "xyes" +then + BUILD_WITH_LIBPCRE_CFLAGS="$with_pcre_cflags" + BUILD_WITH_LIBPCRE_LIBS="$with_pcre_libs" + AC_SUBST(BUILD_WITH_LIBPCRE_CFLAGS) + AC_SUBST(BUILD_WITH_LIBPCRE_LIBS) +fi +AM_CONDITIONAL(BUILD_WITH_LIBPCRE, test "x$with_libpcre" = "xyes") +# }}} + # --with-libperl {{{ perl_interpreter="perl" AC_ARG_WITH(libperl, [AS_HELP_STRING([--with-libperl@<:@=PREFIX@:>@], [Path to libperl.])], @@ -2792,6 +2864,7 @@ AC_PLUGIN([entropy], [$plugin_entropy], [Entropy statistics]) AC_PLUGIN([exec], [yes], [Execution of external programs]) AC_PLUGIN([filecount], [yes], [Count files in directories]) AC_PLUGIN([filter_ignore], [yes], [Ignore specific values]) +AC_PLUGIN([filter_pcre], [$with_libpcre], [Filter based on PCRE]) AC_PLUGIN([hddtemp], [yes], [Query hddtempd]) AC_PLUGIN([interface], [$plugin_interface], [Interface traffic statistics]) AC_PLUGIN([iptables], [$with_libiptc], [IPTables rule counters]) @@ -2927,6 +3000,7 @@ Configuration: libopenipmi . . . . . $with_libopenipmipthread liboping . . . . . . $with_liboping libpcap . . . . . . . $with_libpcap + libpcre . . . . . . . $with_libpcre libperl . . . . . . . $with_libperl libpthread . . . . . $with_libpthread libpq . . . . . . . . $with_libpq @@ -2963,6 +3037,7 @@ Configuration: exec . . . . . . . . $enable_exec filecount . . . . . . $enable_filecount filter_ignore . . . . $enable_filter_ignore + filter_pcre . . . . . $enable_filter_pcre hddtemp . . . . . . . $enable_hddtemp interface . . . . . . $enable_interface iptables . . . . . . $enable_iptables diff --git a/src/Makefile.am b/src/Makefile.am index 2196d32c..fb3587b6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -282,6 +282,16 @@ collectd_LDADD += "-dlopen" filter_ignore.la collectd_DEPENDENCIES += filter_ignore.la endif +if BUILD_PLUGIN_FILTER_PCRE +pkglib_LTLIBRARIES += filter_pcre.la +filter_pcre_la_SOURCES = filter_pcre.c +filter_pcre_la_CPPFLAGS = $(BUILD_WITH_LIBPCRE_CFLAGS) +filter_pcre_la_LDFLAGS = -module -avoid-version \ + $(BUILD_WITH_LIBPCRE_LIBS) +collectd_LDADD += "-dlopen" filter_pcre.la +collectd_DEPENDENCIES += filter_pcre.la +endif + if BUILD_PLUGIN_HDDTEMP pkglib_LTLIBRARIES += hddtemp.la hddtemp_la_SOURCES = hddtemp.c diff --git a/src/collectd.conf.in b/src/collectd.conf.in index 0597273f..4963a060 100644 --- a/src/collectd.conf.in +++ b/src/collectd.conf.in @@ -42,6 +42,7 @@ FQDNLookup true @BUILD_PLUGIN_ENTROPY_TRUE@LoadPlugin entropy @BUILD_PLUGIN_EXEC_TRUE@LoadPlugin exec @BUILD_PLUGIN_FILECOUNT_TRUE@LoadPlugin filecount +@BUILD_PLUGIN_FILTER_PCRE_TRUE@LoadPlugin filter_pcre @BUILD_PLUGIN_HDDTEMP_TRUE@LoadPlugin hddtemp @BUILD_PLUGIN_INTERFACE_TRUE@LoadPlugin interface @BUILD_PLUGIN_IPTABLES_TRUE@LoadPlugin iptables @@ -170,6 +171,16 @@ FQDNLookup true # # +# +# +# Host "^mail\d+$" +# Plugin "^tcpconns$" +# TypeInstance "^SYN_" +# +# Action NoWrite +# +# + @BUILD_PLUGIN_HDDTEMP_TRUE@ # Host "127.0.0.1" # Port "7634" diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 5d07ee52..bc494b8f 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -648,6 +648,70 @@ note that there are 1000 bytes in a kilobyte, not 1024. =back +=head2 Plugin C + +This plugin allows you to filter value lists based on Perl-compatible regular +expressions whose syntax and semantics are as close as possible to those of +the Perl 5 language. See L for details. + + + + Host "^mail\d+$" + Plugin "^tcpconns$" + TypeInstance "^SYN_" + + Action NoWrite + + +The configuration consists of one or more C blocks, each of which +specifies a regular expression identifying a set of value lists and how to +handle successful matches. A value list keeps the values of a single data-set +and is identified by the tuple (host, plugin, plugin instance, type, type +instance). The plugin and type instances are optional components. If they are +missing they are treated as empty strings. Within those blocks, the following +options are recognized: + +=over 4 + +=item B I + +=item B I + +=item B I + +=item B I + +=item B I + +Specifies the regular expression for each component of the identifier. If any +of these options is missing it is interpreted as a pattern which matches any +string. All five components of a value list have to match the appropriate +regular expression to trigger the specified action. + +=item B I|I|I + +Specify how to handle successful matches: + +=over 4 + +=item B + +Do not send the value list to any output (a.k.a. write) plugins. + +=item B + +Skip threshold checking for this value list. + +=item B + +Completely ignore this value list. + +=back + +Two or more actions may be combined by specifying multiple B options. + +=back + =head2 Plugin C To get values from B collectd connects to B (127.0.0.1), @@ -2290,6 +2354,7 @@ L, L, L, L, +L, L, L, L diff --git a/src/filter_pcre.c b/src/filter_pcre.c new file mode 100644 index 00000000..a92dee0b --- /dev/null +++ b/src/filter_pcre.c @@ -0,0 +1,329 @@ +/** + * collectd - src/filter_pcre.c + * Copyright (C) 2008 Sebastian Harl + * + * 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; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: + * Sebastian Harl + **/ + +/* + * This module allows to filter value lists based on Perl-compatible regular + * expressions. + */ + +#include "collectd.h" +#include "configfile.h" +#include "plugin.h" +#include "common.h" + +#include + +#define log_err(...) ERROR ("filter_pcre: " __VA_ARGS__) +#define log_warn(...) WARNING ("filter_pcre: " __VA_ARGS__) + +/* + * private data types + */ + +typedef struct { + pcre *re; + pcre_extra *extra; +} c_pcre_t; + +#define C_PCRE_INIT(regex) do { \ + (regex).re = NULL; \ + (regex).extra = NULL; \ + } while (0) + +#define C_PCRE_FREE(regex) do { \ + pcre_free ((regex).re); \ + pcre_free ((regex).extra); \ + C_PCRE_INIT (regex); \ + } while (0) + +typedef struct { + c_pcre_t host; + c_pcre_t plugin; + c_pcre_t plugin_instance; + c_pcre_t type; + c_pcre_t type_instance; + + int action; +} regex_t; + +/* + * private variables + */ + +static regex_t *regexes = NULL; +static int regexes_num = 0; + +/* + * internal helper functions + */ + +/* returns true if string matches the regular expression */ +static int c_pcre_match (c_pcre_t *re, const char *string) +{ + int status; + int ovector[30]; + + if (NULL == re) + return 1; + + if (NULL == string) + string = ""; + + status = pcre_exec (re->re, + /* extra = */ re->extra, + /* subject = */ string, + /* length = */ strlen (string), + /* startoffset = */ 0, + /* options = */ 0, + /* ovector = */ ovector, + /* ovecsize = */ STATIC_ARRAY_SIZE (ovector)); + + if (0 <= status) + return 1; + + if (PCRE_ERROR_NOMATCH != status) + log_err ("PCRE matching of string \"%s\" failed with status %d", + string, status); + return 0; +} /* c_pcre_match */ + +static regex_t *regex_new (void) +{ + regex_t *re; + + ++regexes_num; + regexes = (regex_t *)realloc (regexes, regexes_num * sizeof (*regexes)); + if (NULL == regexes) { + log_err ("Out of memory."); + exit (5); + } + + re = regexes + (regexes_num - 1); + + C_PCRE_INIT (re->host); + C_PCRE_INIT (re->plugin); + C_PCRE_INIT (re->plugin_instance); + C_PCRE_INIT (re->type); + C_PCRE_INIT (re->type_instance); + + re->action = 0; + return re; +} /* regex_new */ + +static void regex_delete (regex_t *re) +{ + if (NULL == re) + return; + + C_PCRE_FREE (re->host); + C_PCRE_FREE (re->plugin); + C_PCRE_FREE (re->plugin_instance); + C_PCRE_FREE (re->type); + C_PCRE_FREE (re->type_instance); + + re->action = 0; +} /* regex_delete */ + +/* returns true if the value list matches the regular expression */ +static int regex_match (regex_t *re, value_list_t *vl) +{ + int matches = 0; + + if (NULL == re) + return 1; + + if ((NULL == re->host.re) || c_pcre_match (&re->host, vl->host)) + ++matches; + + if ((NULL == re->plugin.re) || c_pcre_match (&re->plugin, vl->plugin)) + ++matches; + + if ((NULL == re->plugin_instance.re) + || c_pcre_match (&re->plugin_instance, vl->plugin_instance)) + ++matches; + + if ((NULL == re->type.re) || c_pcre_match (&re->type, vl->type)) + ++matches; + + if ((NULL == re->type_instance.re) + || c_pcre_match (&re->type_instance, vl->type_instance)) + ++matches; + + if (5 == matches) + return 1; + return 0; +} /* regex_match */ + +/* + * interface to collectd + */ + +static int c_pcre_filter (const data_set_t *ds, value_list_t *vl) +{ + int i; + + for (i = 0; i < regexes_num; ++i) + if (regex_match (regexes + i, vl)) + return regexes[i].action; + return 0; +} /* c_pcre_filter */ + +static int c_pcre_shutdown (void) +{ + int i; + + plugin_unregister_filter ("filter_pcre"); + plugin_unregister_shutdown ("filter_pcre"); + + for (i = 0; i < regexes_num; ++i) + regex_delete (regexes + i); + + sfree (regexes); + regexes_num = 0; + return 0; +} /* c_pcre_shutdown */ + +static int config_set_regex (c_pcre_t *re, oconfig_item_t *ci) +{ + const char *pattern; + const char *errptr; + int erroffset; + + if ((0 != ci->children_num) || (1 != ci->values_num) + || (OCONFIG_TYPE_STRING != ci->values[0].type)) { + log_err (": %s expects a single string argument.", ci->key); + return 1; + } + + pattern = ci->values[0].value.string; + + re->re = pcre_compile (pattern, + /* options = */ 0, + /* errptr = */ &errptr, + /* erroffset = */ &erroffset, + /* tableptr = */ NULL); + + if (NULL == re->re) { + log_err (": PCRE compilation of pattern \"%s\" failed " + "at offset %d: %s", pattern, erroffset, errptr); + return 1; + } + + re->extra = pcre_study (re->re, + /* options = */ 0, + /* errptr = */ &errptr); + + if (NULL != errptr) { + log_err (": PCRE studying of pattern \"%s\" failed: %s", + pattern, errptr); + return 1; + } + return 0; +} /* config_set_regex */ + +static int config_set_action (int *action, oconfig_item_t *ci) +{ + const char *action_str; + + if ((0 != ci->children_num) || (1 != ci->values_num) + || (OCONFIG_TYPE_STRING != ci->values[0].type)) { + log_err (": Action expects a single string argument."); + return 1; + } + + action_str = ci->values[0].value.string; + + if (0 == strcasecmp (action_str, "NoWrite")) + *action |= FILTER_NOWRITE; + else if (0 == strcasecmp (action_str, "NoThresholdCheck")) + *action |= FILTER_NOTHRESHOLD_CHECK; + else if (0 == strcasecmp (action_str, "Ignore")) + *action |= FILTER_IGNORE; + else + log_warn (": Ignoring unknown action \"%s\".", action_str); + return 0; +} /* config_set_action */ + +static int c_pcre_config_regex (oconfig_item_t *ci) +{ + regex_t *re; + int i; + + if (0 != ci->values_num) { + log_err (" expects no arguments."); + return 1; + } + + re = regex_new (); + + for (i = 0; i < ci->children_num; ++i) { + oconfig_item_t *c = ci->children + i; + int status = 0; + + if (0 == strcasecmp (c->key, "Host")) + status = config_set_regex (&re->host, c); + else if (0 == strcasecmp (c->key, "Plugin")) + status = config_set_regex (&re->plugin, c); + else if (0 == strcasecmp (c->key, "PluginInstance")) + status = config_set_regex (&re->plugin_instance, c); + else if (0 == strcasecmp (c->key, "Type")) + status = config_set_regex (&re->type, c); + else if (0 == strcasecmp (c->key, "TypeInstance")) + status = config_set_regex (&re->type_instance, c); + else if (0 == strcasecmp (c->key, "Action")) + status = config_set_action (&re->action, c); + else + log_warn (": Ignoring unknown config key \"%s\".", c->key); + + if (0 != status) { + log_err ("Ignoring regular expression definition."); + regex_delete (re); + --regexes_num; + } + } + return 0; +} /* c_pcre_config_regex */ + +static int c_pcre_config (oconfig_item_t *ci) +{ + int i; + + for (i = 0; i < ci->children_num; ++i) { + oconfig_item_t *c = ci->children + i; + + if (0 == strcasecmp (c->key, "RegEx")) + c_pcre_config_regex (c); + else + log_warn ("Ignoring unknown config key \"%s\".", c->key); + } + + plugin_register_filter ("filter_pcre", c_pcre_filter); + plugin_register_shutdown ("filter_pcre", c_pcre_shutdown); + return 0; +} /* c_pcre_config */ + +void module_register (void) +{ + plugin_register_complex_config ("filter_pcre", c_pcre_config); +} /* module_register */ + +/* vim: set sw=4 ts=4 tw=78 noexpandtab : */ + -- 2.11.0