Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 80 additions & 9 deletions src/libbpfilter/set.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
#include "bpfilter/set.h"

#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
Expand Down Expand Up @@ -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);
Expand All @@ -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(
Expand All @@ -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;
Expand All @@ -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;
}
Expand Down
82 changes: 82 additions & 0 deletions tests/unit/libbpfilter/set.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
Loading