diff --git a/src/bfcli/lexer.l b/src/bfcli/lexer.l index 749d1399..39b2dd1f 100644 --- a/src/bfcli/lexer.l +++ b/src/bfcli/lexer.l @@ -35,6 +35,7 @@ %s STATE_MATCHER_IP4_DSCP %s STATE_MATCHER_IP6_ADDR %s STATE_MATCHER_IP6_NET +%s STATE_MATCHER_IP6_DSCP %s STATE_MATCHER_PORT %s STATE_MATCHER_ICMP_TYPE %s STATE_MATCHER_ICMP_CODE @@ -216,6 +217,16 @@ ip6\.(s|d)net { BEGIN(STATE_MATCHER_IP6_NET); yylval.sval = strdup(yytext) } } +ip6\.dscp { BEGIN(STATE_MATCHER_IP6_DSCP); yylval.sval = strdup(yytext); return MATCHER_TYPE; } +{ + (eq|not) { yylval.sval = strdup(yytext); return MATCHER_OP; } + {int} { + BEGIN(INITIAL); + yylval.sval = strdup(yytext); + return RAW_PAYLOAD; + } +} + meta\.(s|d)port { BEGIN(STATE_MATCHER_PORT); yylval.sval = strdup(yytext); return MATCHER_TYPE; } tcp\.(s|d)port { BEGIN(STATE_MATCHER_PORT); yylval.sval = strdup(yytext); return MATCHER_TYPE; } udp\.(s|d)port { BEGIN(STATE_MATCHER_PORT); yylval.sval = strdup(yytext); return MATCHER_TYPE; } diff --git a/src/bpfilter/cgen/matcher/ip6.c b/src/bpfilter/cgen/matcher/ip6.c index fa35c0e8..88c62578 100644 --- a/src/bpfilter/cgen/matcher/ip6.c +++ b/src/bpfilter/cgen/matcher/ip6.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -293,6 +294,33 @@ static int _bf_matcher_generate_ip6_nexthdr(struct bf_program *program, return 0; } +static int _bf_matcher_generate_ip6_dscp(struct bf_program *program, + const struct bf_matcher *matcher) +{ + uint8_t dscp; + + assert(program); + assert(matcher); + + dscp = *(uint8_t *)bf_matcher_payload(matcher); + + /* IPv6 DSCP (traffic class) spans bits 4-11 of the header: + * Byte 0: version (4 bits) | dscp_high (4 bits) + * Byte 1: dscp_low (4 bits) | flow_label_high (4 bits) + * Load 2 bytes, mask with 0x0ff0, compare against dscp << 4. */ + + EMIT(program, BPF_LDX_MEM(BPF_H, BPF_REG_1, BPF_REG_6, 0)); + EMIT(program, BPF_ALU64_IMM(BPF_AND, BPF_REG_1, 0x0ff0)); + + EMIT_FIXUP_JMP_NEXT_RULE( + program, + BPF_JMP_IMM(bf_matcher_get_op(matcher) == BF_MATCHER_EQ ? BPF_JNE : + BPF_JEQ, + BPF_REG_1, (uint16_t)dscp << 4, 0)); + + return 0; +} + int bf_matcher_generate_ip6(struct bf_program *program, const struct bf_matcher *matcher) { @@ -316,6 +344,9 @@ int bf_matcher_generate_ip6(struct bf_program *program, case BF_MATCHER_IP6_NEXTHDR: r = _bf_matcher_generate_ip6_nexthdr(program, matcher); break; + case BF_MATCHER_IP6_DSCP: + r = _bf_matcher_generate_ip6_dscp(program, matcher); + break; default: return bf_err_r(-EINVAL, "unknown matcher type %d", bf_matcher_get_type(matcher)); diff --git a/src/bpfilter/cgen/program.c b/src/bpfilter/cgen/program.c index b989ca04..63da9fd9 100644 --- a/src/bpfilter/cgen/program.c +++ b/src/bpfilter/cgen/program.c @@ -531,6 +531,7 @@ static int _bf_program_generate_rule(struct bf_program *program, case BF_MATCHER_IP6_DADDR: case BF_MATCHER_IP6_DNET: case BF_MATCHER_IP6_NEXTHDR: + case BF_MATCHER_IP6_DSCP: r = bf_matcher_generate_ip6(program, matcher); if (r) return r; diff --git a/src/libbpfilter/include/bpfilter/matcher.h b/src/libbpfilter/include/bpfilter/matcher.h index 512144d5..15eb970e 100644 --- a/src/libbpfilter/include/bpfilter/matcher.h +++ b/src/libbpfilter/include/bpfilter/matcher.h @@ -86,6 +86,8 @@ enum bf_matcher_type BF_MATCHER_IP6_DNET, /// Matches IPv6 next header BF_MATCHER_IP6_NEXTHDR, + /// Matches against the IPv6 Differentiated Services Code Point (DSCP) field + BF_MATCHER_IP6_DSCP, /// Matches against the TCP source port. Stored as big-endian. BF_MATCHER_TCP_SPORT, /// Matches against the TCP destination port. Stored as big-endian. diff --git a/src/libbpfilter/matcher.c b/src/libbpfilter/matcher.c index 83bca502..08a453dd 100644 --- a/src/libbpfilter/matcher.c +++ b/src/libbpfilter/matcher.c @@ -1094,6 +1094,20 @@ static struct bf_matcher_meta _bf_matcher_metas[_BF_MATCHER_TYPE_MAX] = { _bf_parse_l4_proto, _bf_print_l4_proto), }, }, + [BF_MATCHER_IP6_DSCP] = + { + .layer = BF_MATCHER_LAYER_3, + .hdr_id = ETH_P_IPV6, + .hdr_payload_size = sizeof(uint8_t), + .hdr_payload_offset = 0, + .ops = + { + BF_MATCHER_OPS(BF_MATCHER_EQ, sizeof(uint8_t), + _bf_parse_u8, _bf_print_u8), + BF_MATCHER_OPS(BF_MATCHER_NE, sizeof(uint8_t), + _bf_parse_u8, _bf_print_u8), + }, + }, [BF_MATCHER_TCP_SPORT] = { .layer = BF_MATCHER_LAYER_4, @@ -1461,6 +1475,7 @@ static const char *_bf_matcher_type_strs[] = { [BF_MATCHER_IP6_DADDR] = "ip6.daddr", [BF_MATCHER_IP6_DNET] = "ip6.dnet", [BF_MATCHER_IP6_NEXTHDR] = "ip6.nexthdr", + [BF_MATCHER_IP6_DSCP] = "ip6.dscp", [BF_MATCHER_TCP_SPORT] = "tcp.sport", [BF_MATCHER_TCP_DPORT] = "tcp.dport", [BF_MATCHER_TCP_FLAGS] = "tcp.flags", diff --git a/tests/e2e/CMakeLists.txt b/tests/e2e/CMakeLists.txt index 0a48a96d..c1e6513b 100644 --- a/tests/e2e/CMakeLists.txt +++ b/tests/e2e/CMakeLists.txt @@ -80,6 +80,7 @@ bf_add_e2e_test(e2e matchers/ip4_saddr.sh) bf_add_e2e_test(e2e matchers/ip4_snet.sh) bf_add_e2e_test(e2e matchers/ip6_daddr.sh) bf_add_e2e_test(e2e matchers/ip6_dnet.sh) +bf_add_e2e_test(e2e matchers/ip6_dscp.sh) bf_add_e2e_test(e2e matchers/ip6_nexthdr.sh) bf_add_e2e_test(e2e matchers/ip6_saddr.sh) bf_add_e2e_test(e2e matchers/ip6_snet.sh) diff --git a/tests/e2e/matchers/ip6_dscp.sh b/tests/e2e/matchers/ip6_dscp.sh new file mode 100755 index 00000000..d00f294b --- /dev/null +++ b/tests/e2e/matchers/ip6_dscp.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -eux +set -o pipefail + +. "$(dirname "$0")"/../e2e_test_util.sh + +# Test valid decimal values with 'eq' operator +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 0 counter DROP" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 46 counter DROP" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 255 counter DROP" + +# Test valid hexadecimal values with 'eq' operator +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 0x00 counter DROP" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 0x2e counter DROP" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 0xff counter DROP" + +# Test invalid values with 'eq' operator (should fail) +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq abc counter DROP") +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq -1 counter DROP") +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp eq 256 counter DROP") +(! 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 0x100 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" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 255 counter DROP" + +# Test valid hexadecimal values with 'not' operator +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 0x00 counter DROP" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 0x2e counter DROP" +bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 0xff counter DROP" + +# Test invalid values with 'not' operator (should fail) +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not abc counter DROP") +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not -1 counter DROP") +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 256 counter DROP") +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not -0x01 counter DROP") +(! bfcli ruleset set --dry-run --from-str "chain xdp BF_HOOK_XDP ACCEPT rule ip6.dscp not 0x100 counter DROP") diff --git a/tests/unit/libbpfilter/matcher.c b/tests/unit/libbpfilter/matcher.c index 7a73e5e2..cd097c08 100644 --- a/tests/unit/libbpfilter/matcher.c +++ b/tests/unit/libbpfilter/matcher.c @@ -994,6 +994,93 @@ static void icmpv6_code(void **state) bf_matcher_dump(matcher, &prefix); } +static void ip6_dscp(void **state) +{ + _free_bf_matcher_ struct bf_matcher *matcher = NULL; + const struct bf_matcher_ops *ops; + prefix_t prefix = {}; + + (void)state; + + // Test IPv6 DSCP with decimal value 0 (minimum) + assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "0")); + assert_non_null(matcher); + assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 0); + bf_matcher_dump(matcher, &prefix); + bf_matcher_free(&matcher); + + // Test with decimal value 255 (maximum) + assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "255")); + assert_non_null(matcher); + assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 255); + bf_matcher_dump(matcher, &prefix); + bf_matcher_free(&matcher); + + // Test with different DSCP value (e.g., 46 for EF) + assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "46")); + assert_non_null(matcher); + assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 46); + bf_matcher_dump(matcher, &prefix); + bf_matcher_free(&matcher); + + // Test with NE operator + assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_NE, "8")); + assert_non_null(matcher); + bf_matcher_dump(matcher, &prefix); + bf_matcher_free(&matcher); + + // Test with hexadecimal value (0x2e = 46) + assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "0x2e")); + assert_non_null(matcher); + assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 46); + bf_matcher_dump(matcher, &prefix); + bf_matcher_free(&matcher); + + // Test with another hexadecimal value (0xff = 255) + assert_ok(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "0xff")); + assert_non_null(matcher); + assert_int_equal(*(uint8_t *)bf_matcher_payload(matcher), 255); + bf_matcher_dump(matcher, &prefix); + 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")); + ops = bf_matcher_get_ops(BF_MATCHER_IP6_DSCP, BF_MATCHER_EQ); + assert_non_null(ops); + assert_non_null(ops->print); + ops->print(bf_matcher_payload(matcher)); +} + +static void ip6_dscp_invalid(void **state) +{ + _free_bf_matcher_ struct bf_matcher *matcher = NULL; + + (void)state; + + // Test with value > 255 + assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "256")); + + // Test with invalid string + assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "not_a_number")); + + // Test with negative value + assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "-1")); + + // Test with value > 0xff in hex + assert_err(bf_matcher_new_from_raw(&matcher, BF_MATCHER_IP6_DSCP, + BF_MATCHER_EQ, "0x100")); +} + static void icmpv6_type(void **state) { _free_bf_matcher_ struct bf_matcher *matcher = NULL; @@ -1450,6 +1537,8 @@ int main(void) cmocka_unit_test(ipv6_network_invalid), cmocka_unit_test(icmp_code), cmocka_unit_test(icmpv6_code), + cmocka_unit_test(ip6_dscp), + cmocka_unit_test(ip6_dscp_invalid), cmocka_unit_test(icmpv6_type), cmocka_unit_test(tcp_port_range), cmocka_unit_test(udp_port_range),