diff --git a/src/libbpfilter/set.c b/src/libbpfilter/set.c index a04f0e25..bb3f2296 100644 --- a/src/libbpfilter/set.c +++ b/src/libbpfilter/set.c @@ -6,7 +6,9 @@ #include "bpfilter/set.h" #include +#include #include +#include #include #include #include @@ -161,9 +163,15 @@ int bf_set_add_elem_raw(struct bf_set *set, const char *raw_elem) { _cleanup_free_ void *elem = NULL; _cleanup_free_ char *_raw_elem = NULL; + const struct bf_matcher_ops *range_in_ops = NULL; char *tmp, *saveptr, *token; size_t elem_offset = 0; size_t comp_idx = 0; + size_t range_offset = 0; + enum bf_matcher_type range_type = 0; + uint16_t range_start = 0; + uint16_t range_end = 0; + bool has_range = false; int r; assert(set); @@ -183,6 +191,7 @@ int bf_set_add_elem_raw(struct bf_set *set, const char *raw_elem) tmp = _raw_elem; while ((token = strtok_r(tmp, ",", &saveptr))) { const struct bf_matcher_ops *ops; + const struct bf_matcher_ops *range_ops; if (comp_idx >= set->n_comps) { return bf_err_r( @@ -199,11 +208,41 @@ int bf_set_add_elem_raw(struct bf_set *set, const char *raw_elem) bf_matcher_type_to_str(set->key[comp_idx])); } - r = ops->parse(set->key[comp_idx], BF_MATCHER_IN, elem + elem_offset, - token); - if (r) { - return bf_err_r(r, "failed to parse set element component '%s'", - token); + /* A component is treated as a range if it contains a '-' and its matcher + * type supports the RANGE operator (ports today). This defers writing + * the component's slot until the range is expanded below. */ + range_ops = bf_matcher_get_ops(set->key[comp_idx], BF_MATCHER_RANGE); + if (strchr(token, '-') && range_ops) { + uint16_t bounds[2]; + + if (has_range) { + return bf_err_r( + -EINVAL, + "set element '%s' has more than one ranged component, only one is supported", + raw_elem); + } + + r = range_ops->parse(set->key[comp_idx], BF_MATCHER_RANGE, bounds, + token); + if (r) { + return bf_err_r( + r, "failed to parse set element range component '%s'", + token); + } + + has_range = true; + range_offset = elem_offset; + range_type = set->key[comp_idx]; + range_in_ops = ops; + range_start = bounds[0]; + range_end = bounds[1]; + } else { + r = ops->parse(set->key[comp_idx], BF_MATCHER_IN, + elem + elem_offset, token); + if (r) { + return bf_err_r(r, "failed to parse set element component '%s'", + token); + } } elem_offset += ops->ref_payload_size; @@ -216,11 +255,43 @@ int bf_set_add_elem_raw(struct bf_set *set, const char *raw_elem) raw_elem); } - r = bf_hashset_add(&set->elems, &elem); - if (r == -EEXIST) + if (!has_range) { + r = bf_hashset_add(&set->elems, &elem); + if (r == -EEXIST) + return 0; + if (r) + return bf_err_r(r, "failed to insert element into set"); + return 0; - if (r) - return bf_err_r(r, "failed to insert element into set"); + } + + /* Expand the ranged component into one element per value in [start, end]. + * A uint32_t counter is used so a range ending at UINT16_MAX terminates. */ + for (uint32_t value = range_start; value <= range_end; ++value) { + _cleanup_free_ void *row = NULL; + char valbuf[16]; + + (void)snprintf(valbuf, sizeof(valbuf), "%" PRIu32, value); + + r = range_in_ops->parse(range_type, BF_MATCHER_IN, elem + range_offset, + valbuf); + if (r) { + return bf_err_r(r, "failed to parse expanded range value '%s'", + valbuf); + } + + row = malloc(set->elem_size); + if (!row) + return bf_err_r(-ENOMEM, "failed to allocate a new set element"); + + memcpy(row, elem, set->elem_size); + + r = bf_hashset_add(&set->elems, &row); + if (r == -EEXIST) + continue; + if (r) + return bf_err_r(r, "failed to insert element into set"); + } return 0; } diff --git a/tests/unit/libbpfilter/set.c b/tests/unit/libbpfilter/set.c index b1a84124..a6f31ee1 100644 --- a/tests/unit/libbpfilter/set.c +++ b/tests/unit/libbpfilter/set.c @@ -277,6 +277,83 @@ static void new_from_raw_multiple_keys(void **state) assert_int_equal(bf_hashset_size(&set->elems), 2); } +static void new_from_raw_port_range(void **state) +{ + _free_bf_set_ struct bf_set *set = NULL; + + (void)state; + + // A range component expands into one element per value in [start, end]. + // Elements are separated by ';' (',' separates components within an + // element), so the third element '33-44' expands to 12 ports. + assert_ok(bf_set_new_from_raw(&set, "test_range", "(tcp.dport)", + "{11; 22; 33-44}")); + assert_non_null(set); + assert_int_equal(set->n_comps, 1); + assert_int_equal(set->key[0], BF_MATCHER_TCP_DPORT); + assert_int_equal(bf_hashset_size(&set->elems), 14); // 11, 22, 33..44 +} + +static void new_from_raw_port_range_boundaries(void **state) +{ + _free_bf_set_ struct bf_set *set = NULL; + + (void)state; + + // Smallest range: 0 and 1. + assert_ok(bf_set_new_from_raw(&set, "test_low", "(tcp.dport)", "{0-1}")); + assert_int_equal(bf_hashset_size(&set->elems), 2); + bf_set_free(&set); + + // Degenerate single-value range at the upper bound. + assert_ok( + bf_set_new_from_raw(&set, "test_high", "(tcp.dport)", "{65535-65535}")); + assert_int_equal(bf_hashset_size(&set->elems), 1); +} + +static void new_from_raw_port_range_invalid(void **state) +{ + _free_bf_set_ struct bf_set *set = NULL; + + (void)state; + + // End before start. + assert_err(bf_set_new_from_raw(&set, "test", "(tcp.dport)", "{44-33}")); + + // Missing end bound. + assert_err(bf_set_new_from_raw(&set, "test", "(tcp.dport)", "{33-}")); + + // Missing start bound. + assert_err(bf_set_new_from_raw(&set, "test", "(tcp.dport)", "{-44}")); +} + +static void new_from_raw_range_multi_comp(void **state) +{ + _free_bf_set_ struct bf_set *set = NULL; + + (void)state; + + // A range in one component of a multi-component key expands that component + // while the others stay fixed. + assert_ok(bf_set_new_from_raw(&set, "test_multi_range", + "(ip4.daddr, tcp.sport)", + "{1.2.3.4, 80-82}")); + assert_non_null(set); + assert_int_equal(set->n_comps, 2); + assert_int_equal(bf_hashset_size(&set->elems), 3); // ports 80, 81, 82 +} + +static void new_from_raw_range_multi_comp_both(void **state) +{ + _free_bf_set_ struct bf_set *set = NULL; + + (void)state; + + // Only one ranged component per element is supported. + assert_err(bf_set_new_from_raw(&set, "test_both_ranges", + "(tcp.sport, tcp.dport)", "{1-2, 3-4}")); +} + static void new_from_raw_invalid(void **state) { _free_bf_set_ struct bf_set *set = NULL; @@ -472,6 +549,11 @@ int main(void) cmocka_unit_test(dump_empty), cmocka_unit_test(new_from_raw), cmocka_unit_test(new_from_raw_multiple_keys), + cmocka_unit_test(new_from_raw_port_range), + cmocka_unit_test(new_from_raw_port_range_boundaries), + cmocka_unit_test(new_from_raw_port_range_invalid), + cmocka_unit_test(new_from_raw_range_multi_comp), + cmocka_unit_test(new_from_raw_range_multi_comp_both), cmocka_unit_test(new_from_raw_invalid), cmocka_unit_test(add_many_basic), cmocka_unit_test(add_many_mismatched_key_count),