/* Leave a hint that this is in fact a valid number. */
node->field.value_type = EVENT_FIELD_VALUE_TYPE_INTMAX;
node->type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT;
+ } else if (net_parse_range(b, &node->field.value.ip,
+ &node->field.value.ip_bits) == 0) {
+ /* Leave a hint that this is in fact a valid IP. */
+ node->field.value_type = EVENT_FIELD_VALUE_TYPE_IP;
+ node->type = EVENT_FILTER_NODE_TYPE_EVENT_FIELD_EXACT;
} else {
/* This field contains no valid number.
Either this is a string that contains a size unit, a
bool ambiguous_unit:1;
bool warned_ambiguous_unit:1;
bool warned_string_inequality:1;
+ bool warned_ip_inequality:1;
bool warned_type_mismatch:1;
bool warned_timeval_not_implemented:1;
};
}
return FALSE;
}
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ if (node->op != EVENT_FILTER_OP_CMP_EQ) {
+ /* we only support IP equality comparisons */
+ if (!node->warned_ip_inequality) {
+ const char *name = event->sending_name;
+ /* Use i_warning to prevent event filter recursions. */
+ i_warning("Event filter for IP field '%s' "
+ "only supports equality operation "
+ "'=' not '%s'. (event=%s, source=%s:%u)",
+ wanted_field->key,
+ event_filter_export_query_expr_op(node->op),
+ name != NULL ? name : "",
+ source_filename, source_linenum);
+ node->warned_ip_inequality = TRUE;
+ }
+ return FALSE;
+ }
+ if (wanted_field->value_type == EVENT_FIELD_VALUE_TYPE_IP) {
+ return net_is_in_network(&field->value.ip,
+ &wanted_field->value.ip,
+ wanted_field->value.ip_bits);
+ }
+ if (use_strcmp) {
+ /* If the matched value was a number, it was already
+ matched in the previous branch. So here we have a
+ non-wildcard IP, which can never be a match to an
+ IP. */
+ if (!node->warned_type_mismatch) {
+ const char *name = event->sending_name;
+ /* Use i_warning to prevent event filter recursions. */
+ i_warning("Event filter matches IP field "
+ "'%s' against non-IP value '%s'. "
+ "(event=%s, source=%s:%u)",
+ wanted_field->key,
+ wanted_field->value.str,
+ name != NULL ? name : "",
+ source_filename, source_linenum);
+ node->warned_type_mismatch = TRUE;
+ }
+ return FALSE;
+ }
+ bool ret;
+ T_BEGIN {
+ ret = wildcard_match_icase(net_ip2addr(&field->value.ip),
+ wanted_field->value.str);
+ } T_END;
+ return ret;
case EVENT_FIELD_VALUE_TYPE_STRLIST:
/* check if the value is (or is not) on the list,
only string matching makes sense here. */
EVENT_CODE_FIELD_INTMAX = 'I',
EVENT_CODE_FIELD_STR = 'S',
EVENT_CODE_FIELD_TIMEVAL = 'T',
+ EVENT_CODE_FIELD_IP = 'P',
EVENT_CODE_FIELD_STRLIST = 'L',
};
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
event_add_timeval(to, fld->key, &fld->value.timeval);
break;
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ event_add_ip(to, fld->key, &fld->value.ip);
+ break;
case EVENT_FIELD_VALUE_TYPE_STRLIST:
values = array_get(&fld->value.strlist, &count);
for (unsigned int i = 0; i < count; i++)
return t_strdup_printf("%"PRIdTIME_T".%u",
field->value.timeval.tv_sec,
(unsigned int)field->value.timeval.tv_usec);
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ return net_ip2addr(&field->value.ip);
case EVENT_FIELD_VALUE_TYPE_STRLIST: {
ARRAY_TYPE(const_string) list;
t_array_init(&list, 8);
return event;
}
+struct event *
+event_add_ip(struct event *event, const char *key, const struct ip_addr *ip)
+{
+ struct event_field *field;
+
+ if (ip->family == 0) {
+ /* ignore nonexistent IP (similar to
+ event_add_str(value=NULL)) */
+ if (event_find_field_recursive(event, key) != NULL)
+ event_field_clear(event, key);
+ return event;
+ }
+
+ field = event_get_field(event, key, TRUE);
+ field->value_type = EVENT_FIELD_VALUE_TYPE_IP;
+ field->value.ip = *ip;
+ return event;
+}
+
struct event *
event_add_fields(struct event *event,
const struct event_add_field *fields)
else if (fields[i].value_timeval.tv_sec != 0) {
event_add_timeval(event, fields[i].key,
&fields[i].value_timeval);
+ } else if (fields[i].value_ip.family != 0) {
+ event_add_ip(event, fields[i].key, &fields[i].value_ip);
} else {
event_add_int(event, fields[i].key,
fields[i].value_intmax);
field->value.timeval.tv_sec,
(unsigned int)field->value.timeval.tv_usec);
break;
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ str_append_c(dest, EVENT_CODE_FIELD_IP);
+ str_append_tabescaped(dest, field->key);
+ str_printfa(dest, "\t%s", net_ip2addr(&field->value.ip));
+ break;
case EVENT_FIELD_VALUE_TYPE_STRLIST: {
unsigned int count;
const char *const *strlist =
}
args++;
break;
+ case EVENT_CODE_FIELD_IP:
+ field->value_type = EVENT_FIELD_VALUE_TYPE_IP;
+ if (net_addr2ip(*args, &field->value.ip) < 0) {
+ *error_r = t_strdup_printf(
+ "Invalid field value '%s' IP for '%s'",
+ *args, field->key);
+ return FALSE;
+ }
+ break;
case EVENT_CODE_FIELD_STRLIST:
if (!event_import_strlist(event, field, &args, error_r))
return FALSE;
case EVENT_CODE_FIELD_INTMAX:
case EVENT_CODE_FIELD_STR:
case EVENT_CODE_FIELD_STRLIST:
- case EVENT_CODE_FIELD_TIMEVAL: {
+ case EVENT_CODE_FIELD_TIMEVAL:
+ case EVENT_CODE_FIELD_IP: {
args++;
if (!event_import_field(event, code, arg, &args, error_r))
return FALSE;
return &event_passthrough_vfuncs;
}
+static struct event_passthrough *
+event_passthrough_add_ip(const char *key, const struct ip_addr *ip)
+{
+ event_add_ip(last_passthrough_event(), key, ip);
+ return &event_passthrough_vfuncs;
+}
+
static struct event_passthrough *
event_passthrough_inc_int(const char *key, intmax_t num)
{
.add_int = event_passthrough_add_int,
.add_int_nonzero = event_passthrough_add_int_nonzero,
.add_timeval = event_passthrough_add_timeval,
+ .add_ip = event_passthrough_add_ip,
.inc_int = event_passthrough_inc_int,
.strlist_append = event_passthrough_strlist_append,
.strlist_replace = event_passthrough_strlist_replace,
/* event.h name is probably a bit too generic, so lets avoid using it. */
#include <sys/time.h>
+#include "net.h"
/* Field name for the reason_code string list. */
#define EVENT_REASON_CODE "reason_code"
EVENT_FIELD_VALUE_TYPE_STR,
EVENT_FIELD_VALUE_TYPE_INTMAX,
EVENT_FIELD_VALUE_TYPE_TIMEVAL,
+ EVENT_FIELD_VALUE_TYPE_IP,
EVENT_FIELD_VALUE_TYPE_STRLIST,
};
const char *str;
intmax_t intmax;
struct timeval timeval;
+ struct ip_addr ip;
+ unsigned int ip_bits; /* set for event filters */
ARRAY_TYPE(const_string) strlist;
} value;
};
const char *value;
intmax_t value_intmax;
struct timeval value_timeval;
+ struct ip_addr value_ip;
};
struct event_passthrough {
(*add_int_nonzero)(const char *key, intmax_t num);
struct event_passthrough *
(*add_timeval)(const char *key, const struct timeval *tv);
+ struct event_passthrough *
+ (*add_ip)(const char *key, const struct ip_addr *ip);
struct event_passthrough *
(*inc_int)(const char *key, intmax_t num);
struct event *
event_add_timeval(struct event *event, const char *key,
const struct timeval *tv);
+struct event *
+event_add_ip(struct event *event, const char *key, const struct ip_addr *ip);
/* Append new value to list. If the key is not a list, it will
be cleared first. NULL values are ignored. Duplicate values are ignored. */
struct event *
"foo.c",
"foo.c:123",
+ "0",
+ "123",
+ "123*",
+
+ "127.0.0.1",
+ "127.0.0.*",
+
/* wildcards */
"*foo",
"f*o",
test_end();
}
+static struct ip_addr test_addr2ip(const char *addr)
+{
+ struct ip_addr ip;
+ if (net_addr2ip(addr, &ip) < 0)
+ i_unreached();
+ return ip;
+}
+
+static void test_event_filter_ips(void)
+{
+ struct event_filter *filter;
+ const char *error;
+ struct ip_addr ip;
+ const struct failure_context failure_ctx = {
+ .type = LOG_TYPE_DEBUG
+ };
+
+ test_begin("event filter: event ip matching");
+
+ filter = event_filter_create();
+ test_assert(event_filter_parse("ip = 127.0.0.1", filter, &error) == 0);
+
+ struct event *e = event_create(NULL);
+ /* ip match */
+ test_assert(net_addr2ip("127.0.0.1", &ip) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* ip mismatch */
+ test_assert(net_addr2ip("127.0.0.2", &ip) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ /* string ip match */
+ event_add_str(e, "ip", "127.0.0.1");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* numeric ip mismatch */
+ event_add_int(e, "ip", 2130706433);
+ test_expect_error_string("Event filter matches integer field 'ip' "
+ "against non-integer value '127.0.0.1'");
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ test_expect_no_more_errors();
+ event_filter_unref(&filter);
+
+ filter = event_filter_create();
+ test_assert(event_filter_parse("ip = 127.0.0.*", filter, &error) == 0);
+ /* wildcard match */
+ test_assert(net_addr2ip("127.0.0.1", &ip) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* wildcard mismatch */
+ test_assert(net_addr2ip("127.0.1.1", &ip) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ /* wildcard match as string */
+ event_add_str(e, "ip", "127.0.0.3");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ filter = event_filter_create();
+ test_assert(event_filter_parse("ip = \"127.0.0.0/16\"", filter, &error) == 0);
+ /* network mask match */
+ test_assert(net_addr2ip("127.0.255.255", &ip) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ /* network mask mismatch */
+ test_assert(net_addr2ip("127.1.255.255", &ip) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ /* network mask mismatch as string */
+ event_add_str(e, "ip", "127.0.123.45");
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ /* network mask match as string */
+ event_add_str(e, "ip", "127.0.0.0/16");
+ test_assert(event_filter_match(filter, e, &failure_ctx));
+ event_filter_unref(&filter);
+
+ filter = event_filter_create();
+ test_assert(event_filter_parse("ip = fish", filter, &error) == 0);
+ event_add_ip(e, "ip", &ip);
+ test_expect_error_string("Event filter matches IP field 'ip' "
+ "against non-IP value 'fish'");
+ test_assert(!event_filter_match(filter, e, &failure_ctx));
+ test_expect_no_more_errors();
+ event_filter_unref(&filter);
+
+ const struct {
+ const char *filter;
+ struct ip_addr ip;
+ bool match;
+ } tests[] = {
+ { "ip = ::1", test_addr2ip("::1"), TRUE },
+ { "ip = ::2", test_addr2ip("::1"), FALSE },
+
+ { "ip = \"::1/128\"", test_addr2ip("::1"), TRUE },
+ { "ip = \"::1/126\"", test_addr2ip("::2"), TRUE },
+ { "ip = \"::1/126\"", test_addr2ip("::3"), TRUE },
+ { "ip = \"::1/126\"", test_addr2ip("::4"), FALSE },
+
+ { "ip = \"2001::/8\"", test_addr2ip("2001::1"), TRUE },
+ { "ip = \"2001::/8\"", test_addr2ip("20ff:ffff::1"), TRUE },
+ { "ip = \"2001::/8\"", test_addr2ip("2100::1"), FALSE },
+
+ { "ip = 2001::1", test_addr2ip("2001::1"), TRUE },
+ { "ip = \"2001::1\"", test_addr2ip("2001::1"), TRUE },
+ { "ip = 2001:0:0:0:0:0:0:1", test_addr2ip("2001::1"), TRUE },
+ { "ip = 2001::1", test_addr2ip("2001::2"), FALSE },
+
+ { "ip = 2000:1190:c02a:130:a87a:ad7:5b76:3310",
+ test_addr2ip("2000:1190:c02a:130:a87a:ad7:5b76:3310"), TRUE },
+ { "ip = 2001:1190:c02a:130:a87a:ad7:5b76:3310",
+ test_addr2ip("2000:1190:c02a:130:a87a:ad7:5b76:3310"), FALSE },
+
+ { "ip = \"fe80::1%lo\"", test_addr2ip("fe80::1%lo"), TRUE },
+ };
+ for (unsigned int i = 0; i < N_ELEMENTS(tests); i++) {
+ filter = event_filter_create();
+ test_assert_idx(event_filter_parse(tests[i].filter, filter, &error) == 0, i);
+ event_add_ip(e, "ip", &tests[i].ip);
+ test_assert_idx(event_filter_match(filter, e, &failure_ctx) == tests[i].match, i);
+ event_filter_unref(&filter);
+ }
+
+ event_unref(&e);
+ test_end();
+}
+
static void test_event_filter_size_values(void)
{
const char *error;
test_event_filter_named_separate_from_str();
test_event_filter_duration();
test_event_filter_numbers();
+ test_event_filter_ips();
test_event_filter_size_values();
test_event_filter_interval_values();
test_event_filter_ambiguous_units();
test_assert(timeval_cmp(&exp[i].value.timeval,
&got[i].value.timeval) == 0);
break;
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ test_assert(net_ip_compare(&exp[i].value.ip,
+ &got[i].value.ip));
+ break;
case EVENT_FIELD_VALUE_TYPE_STRLIST:
got_str = t_array_const_string_join(&got[i].value.strlist, ",");
test_assert_strcmp(exp[i].value.str, got_str);
test_begin("event fields");
struct event *event = event_create(NULL);
struct event_field *field;
+ struct ip_addr ip = { .family = 0 };
event_add_str(event, "key", NULL);
test_assert(event_find_field_nonrecursive(event, "key") == NULL);
+ event_add_ip(event, "key", &ip);
+ test_assert(event_find_field_nonrecursive(event, "key") == NULL);
event_add_str(event, "key", "value1");
field = event_find_field_nonrecursive(event, "key");
field->value.timeval.tv_sec == tv.tv_sec &&
field->value.timeval.tv_usec == tv.tv_usec);
+ if (net_addr2ip("1002::4301:6", &ip) < 0)
+ i_unreached();
+ event_add_ip(event, "key", &ip);
+ field = event_find_field_nonrecursive(event, "key");
+ test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_IP &&
+ net_ip_compare(&field->value.ip, &ip));
+
+ ip.family = 0;
+ event_add_ip(event, "key", &ip);
+ field = event_find_field_nonrecursive(event, "key");
+ test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_STR &&
+ field->value.str[0] == '\0');
+
event_strlist_append(event, "key", "strlist1");
field = event_find_field_nonrecursive(event, "key");
test_assert(field != NULL && field->value_type == EVENT_FIELD_VALUE_TYPE_STRLIST &&
}
}
+static void append_ip(string_t *dest, const struct ip_addr *ip)
+{
+ str_append_c(dest, '"');
+ str_append(dest, net_ip2addr(ip));
+ str_append_c(dest, '"');
+}
+
static void append_field_value(string_t *dest, const struct event_field *field,
const struct metric_export_info *info)
{
append_time(dest, &field->value.timeval,
info->exporter->time_format);
break;
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ append_ip(dest, &field->value.ip);
+ break;
case EVENT_FIELD_VALUE_TYPE_STRLIST:
append_strlist(dest, &field->value.strlist, info);
break;
}
}
+static void append_ip(string_t *dest, const struct ip_addr *ip)
+{
+ str_append(dest, net_ip2addr(ip));
+}
+
static void append_field_str(string_t *dest, const char *str,
const struct metric_export_info *info)
{
append_time(dest, &field->value.timeval,
info->exporter->time_format);
break;
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ append_ip(dest, &field->value.ip);
+ break;
case EVENT_FIELD_VALUE_TYPE_STRLIST:
append_strlist(dest, &field->value.strlist);
break;
if (sub_metrics->group_value.intmax == value->intmax)
return sub_metrics;
break;
+ case METRIC_VALUE_TYPE_IP:
+ if (net_ip_compare(&sub_metrics->group_value.ip,
+ &value->ip))
+ return sub_metrics;
+ break;
case METRIC_VALUE_TYPE_BUCKET_INDEX:
if (sub_metrics->group_value.intmax == value->intmax)
return sub_metrics;
return TRUE;
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
return FALSE;
+ case EVENT_FIELD_VALUE_TYPE_IP:
+ value_r->type = METRIC_VALUE_TYPE_IP;
+ value_r->ip = field->value.ip;
+ return TRUE;
case EVENT_FIELD_VALUE_TYPE_STRLIST:
return FALSE;
}
switch (field->value_type) {
case EVENT_FIELD_VALUE_TYPE_STR:
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+ case EVENT_FIELD_VALUE_TYPE_IP:
case EVENT_FIELD_VALUE_TYPE_STRLIST:
return FALSE;
case EVENT_FIELD_VALUE_TYPE_INTMAX:
switch (field->value_type) {
case EVENT_FIELD_VALUE_TYPE_STR:
case EVENT_FIELD_VALUE_TYPE_TIMEVAL:
+ case EVENT_FIELD_VALUE_TYPE_IP:
case EVENT_FIELD_VALUE_TYPE_STRLIST:
i_unreached();
case EVENT_FIELD_VALUE_TYPE_INTMAX:
return field->value.str;
case METRIC_VALUE_TYPE_INT:
return dec2str(field->value.intmax);
+ case METRIC_VALUE_TYPE_IP:
+ return net_ip2addr(&field->value.ip);
case METRIC_VALUE_TYPE_BUCKET_INDEX:
return stats_metric_group_by_get_label(field, group_by, value);
}
}
sub_metric->group_value.type = value->type;
sub_metric->group_value.intmax = value->intmax;
+ sub_metric->group_value.ip = value->ip;
memcpy(sub_metric->group_value.hash, value->hash, SHA1_RESULTLEN);
return sub_metric;
}
switch (field->value_type) {
case EVENT_FIELD_VALUE_TYPE_STR:
case EVENT_FIELD_VALUE_TYPE_STRLIST:
+ case EVENT_FIELD_VALUE_TYPE_IP:
break;
case EVENT_FIELD_VALUE_TYPE_INTMAX:
num = field->value.intmax;
enum metric_value_type {
METRIC_VALUE_TYPE_STR,
METRIC_VALUE_TYPE_INT,
+ METRIC_VALUE_TYPE_IP,
METRIC_VALUE_TYPE_BUCKET_INDEX,
};
enum metric_value_type type;
unsigned char hash[SHA1_RESULTLEN];
intmax_t intmax;
+ struct ip_addr ip;
};
struct metric {