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
16 changes: 14 additions & 2 deletions doc/usage/bfcli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,12 @@ IPv4
- :rspan:`1` ``$PROTOCOL``
- :rspan:`1` ``$PROTOCOL`` must be a transport layer protocol name (e.g. "ICMP", case insensitive), or a valid decimal or hexadecimal `internet protocol number`_.
* - ``not``
* - :rspan:`1` DSCP
- :rspan:`1` ``ip4.dscp``
- ``eq``
- :rspan:`1` ``$DSCP``
- :rspan:`1` ``$DSCP`` is a DSCP class name (e.g. "ef", "cs1", "af21", case insensitive) or a numeric value (0-63, decimal or hexadecimal). "be" is accepted as an alias for "cs0".
* - ``not``


IPv6
Expand Down Expand Up @@ -675,8 +681,14 @@ IPv6
* - :rspan:`1` Next header
- :rspan:`1` ``ip6.nexthdr``
- ``eq``
- :rspan:`3` ``$NEXT_HEADER``
- :rspan:`3` ``$NEXT_HEADER`` is a transport layer protocol name (e.g. "ICMP", case insensitive), an IPv6 extension header name, or a valid decimal or hexadecimal `internet protocol number`_.
- :rspan:`1` ``$NEXT_HEADER``
- :rspan:`1` ``$NEXT_HEADER`` is a transport layer protocol name (e.g. "ICMP", case insensitive), an IPv6 extension header name, or a valid decimal or hexadecimal `internet protocol number`_.
* - ``not``
* - :rspan:`1` DSCP
- :rspan:`1` ``ip6.dscp``
- ``eq``
- :rspan:`1` ``$DSCP``
- :rspan:`1` ``$DSCP`` is a DSCP class name (e.g. "ef", "cs1", "af21", case insensitive) or a numeric value (0-63, decimal or hexadecimal). "be" is accepted as an alias for "cs0".
* - ``not``

.. tip::
Expand Down
3 changes: 3 additions & 0 deletions src/libbpfilter/include/bpfilter/matcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,9 @@ int bf_ethertype_from_str(const char *str, uint16_t *ethertype);
const char *bf_ipproto_to_str(uint8_t ipproto);
int bf_ipproto_from_str(const char *str, uint8_t *ipproto);

const char *bf_dscp_class_to_str(uint8_t dscp);
int bf_dscp_class_from_str(const char *str, uint8_t *dscp);

const char *bf_icmp_type_to_str(uint8_t type);
int bf_icmp_type_from_str(const char *str, uint8_t *type);

Expand Down
57 changes: 55 additions & 2 deletions src/libbpfilter/matcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -737,22 +737,30 @@ static int _bf_parse_dscp(enum bf_matcher_type type, enum bf_matcher_op op,
assert(payload);
assert(raw_payload);

if (!bf_dscp_class_from_str(raw_payload, (uint8_t *)payload))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: nit: Other _bf_parse_* functions pass payload directly to their corresponding *_from_str call without a cast (e.g. bf_icmp_type_from_str(raw_payload, payload), bf_ipproto_from_str(raw_payload, payload)). The explicit (uint8_t *) cast is safe but unnecessary in C (void * converts implicitly) and inconsistent with the existing pattern.

return 0;

if (!_bf_strtoul(raw_payload, &value) && value <= BF_DSCP_MAX) {
*(uint8_t *)payload = (uint8_t)value;
return 0;
}

return bf_err_r(
-EINVAL,
"\"%s %s\" expects a DSCP value (0-63) in decimal or hexadecimal notation, not '%s'",
"\"%s %s\" expects a DSCP value (0-63, decimal/hex) or class name, not '%s'",
bf_matcher_type_to_str(type), bf_matcher_op_to_str(op), raw_payload);
}

static void _bf_print_dscp(const void *payload)
{
assert(payload);

(void)fprintf(stdout, "%" PRIu8, *(uint8_t *)payload);
const char *name = bf_dscp_class_to_str(*(uint8_t *)payload);

if (name)
(void)fprintf(stdout, "%s", name);
else
(void)fprintf(stdout, "%" PRIu8, *(uint8_t *)payload);
}

static int _bf_parse_icmpv6_type(enum bf_matcher_type type,
Expand Down Expand Up @@ -1665,6 +1673,51 @@ int bf_ipproto_from_str(const char *str, uint8_t *ipproto)
return -EINVAL;
}

/* DSCP class name to codepoint mapping, based on iptables
* dscp_helper.c and http://www.iana.org/assignments/dscp-registry.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: nit: The IANA link uses plain HTTP. Consider updating to https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml.

* BE is an alias for CS0. */
static const struct
{
const char *name;
uint8_t dscp;
} _bf_dscp_classes[] = {
{"cs0", 0}, {"cs1", 8}, {"cs2", 16}, {"cs3", 24}, {"cs4", 32},
{"cs5", 40}, {"cs6", 48}, {"cs7", 56}, {"af11", 10}, {"af12", 12},
{"af13", 14}, {"af21", 18}, {"af22", 20}, {"af23", 22}, {"af31", 26},
{"af32", 28}, {"af33", 30}, {"af41", 34}, {"af42", 36}, {"af43", 38},
{"ef", 46},
};

const char *bf_dscp_class_to_str(uint8_t dscp)
{
for (size_t i = 0; i < ARRAY_SIZE(_bf_dscp_classes); ++i) {
if (_bf_dscp_classes[i].dscp == dscp)
return _bf_dscp_classes[i].name;
}

return NULL;
}

int bf_dscp_class_from_str(const char *str, uint8_t *dscp)
{
assert(str);
assert(dscp);

if (bf_streq_i(str, "be")) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude: suggestion: The "be" alias is handled as a special case before the main loop. Adding {"be", 0} at the end of _bf_dscp_classes would fold it into the generic loop, removing this branch. bf_dscp_class_to_str(0) would still return "cs0" because it appears first in the array, preserving the canonicalization behavior.

*dscp = 0;
return 0;
}

for (size_t i = 0; i < ARRAY_SIZE(_bf_dscp_classes); ++i) {
if (bf_streq_i(str, _bf_dscp_classes[i].name)) {
*dscp = _bf_dscp_classes[i].dscp;
return 0;
}
}

return -EINVAL;
}

#define ICMP_ROUTERADVERT 9
#define ICMP_ROUTERSOLICIT 10

Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/parsing/ip4_dscp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp eq invalid counter DROP")
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp eq 0x40 counter DROP")

# Test valid class name keywords
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp eq ef counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp not cs1 counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp eq AF21 counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp not BE counter DROP"

# Test invalid class name keywords (should fail)
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp eq cs8 counter DROP")
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp eq AF14 counter DROP")

# Test with 'not' operator
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp not 0 counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip4.dscp not 16 counter DROP"
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/parsing/ip6_dscp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq -0x01 counter DROP")
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 0x40 counter DROP")

# Test valid class name keywords
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq ef counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not cs1 counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq AF21 counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not BE counter DROP"

# Test invalid class name keywords (should fail)
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq cs8 counter DROP")
(! ${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq AF14 counter DROP")

# Test valid decimal values with 'not' operator
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 0 counter DROP"
${BFCLI} ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 46 counter DROP"
Expand Down
71 changes: 69 additions & 2 deletions tests/unit/libbpfilter/matcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,40 @@ static void ipproto_conversions(void **state)
assert_err(bf_ipproto_from_str("invalid", &ipproto));
}

static void dscp_class_conversions(void **state)
{
uint8_t dscp;

(void)state;

// Test to_str for known classes
assert_string_equal(bf_dscp_class_to_str(0), "cs0");
assert_string_equal(bf_dscp_class_to_str(46), "ef");

// Test to_str for values without a named class
assert_null(bf_dscp_class_to_str(63));
assert_null(bf_dscp_class_to_str(64));

// Test from_str
assert_ok(bf_dscp_class_from_str("ef", &dscp));
assert_int_equal(dscp, 46);

assert_ok(bf_dscp_class_from_str("af21", &dscp));
assert_int_equal(dscp, 18);

// Test BE alias and case insensitivity
assert_ok(bf_dscp_class_from_str("BE", &dscp));
assert_int_equal(dscp, 0);

assert_ok(bf_dscp_class_from_str("Af11", &dscp));
assert_int_equal(dscp, 10);

// Test invalid
assert_err(bf_dscp_class_from_str("cs8", &dscp));
assert_err(bf_dscp_class_from_str("af14", &dscp));
assert_err(bf_dscp_class_from_str("invalid", &dscp));
}

static void icmp_type_conversions(void **state)
{
uint8_t type;
Expand Down Expand Up @@ -1111,6 +1145,20 @@ static void ip6_dscp(void **state)
bf_matcher_dump(matcher, &prefix);
bf_matcher_free(&matcher);

// Test with class name keyword
assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP,
BF_MATCHER_EQ, "ef"));
assert_non_null(matcher);
assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 46);
bf_matcher_free(&matcher);

// Test with BE alias (case insensitive)
assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP,
BF_MATCHER_EQ, "BE"));
assert_non_null(matcher);
assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 0);
bf_matcher_free(&matcher);

// Test print function via ops
assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP,
BF_MATCHER_EQ, "0x20"));
Expand All @@ -1130,9 +1178,9 @@ static void ip6_dscp_invalid(void **state)
assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP,
BF_MATCHER_EQ, "64"));

// Test with invalid string
// Test with invalid class name
assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP,
BF_MATCHER_EQ, "not_a_number"));
BF_MATCHER_EQ, "cs8"));

// Test with negative value
assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP,
Expand Down Expand Up @@ -1573,6 +1621,20 @@ static void ip4_dscp(void **state)
assert_non_null(matcher);
bf_matcher_free(&matcher);

// Test ip4.dscp with class name keyword
assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP4_DSCP,
BF_MATCHER_EQ, "ef"));
assert_non_null(matcher);
assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 46);
bf_matcher_free(&matcher);

// Test ip4.dscp with BE alias (case insensitive)
assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP4_DSCP,
BF_MATCHER_EQ, "BE"));
assert_non_null(matcher);
assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 0);
bf_matcher_free(&matcher);

// Test ip4.dscp print function
assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP4_DSCP,
BF_MATCHER_EQ, "0x3f"));
Expand All @@ -1589,6 +1651,10 @@ static void ip4_dscp(void **state)
assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP4_DSCP,
BF_MATCHER_EQ, "0x40"));

// Test ip4.dscp with invalid class name
assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP4_DSCP,
BF_MATCHER_EQ, "cs8"));

// Test ip4.dscp with invalid string
assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP4_DSCP,
BF_MATCHER_EQ, "invalid"));
Expand Down Expand Up @@ -1738,6 +1804,7 @@ int main(void)
cmocka_unit_test(tcp_flag_conversions),
cmocka_unit_test(ethertype_conversions),
cmocka_unit_test(ipproto_conversions),
cmocka_unit_test(dscp_class_conversions),
cmocka_unit_test(icmp_type_conversions),
cmocka_unit_test(icmpv6_type_conversions),
cmocka_unit_test(get_meta),
Expand Down
Loading