Re: [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation
Ido Schimmel <[email protected]> Tue, 7 Apr 2026 16:45:28 +0300
| Newsgroups | dev.linux.lists.bridge,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.netdev |
|---|---|
| Message-ID | <20260407134528.GE849209@shredder> |
On Fri, Apr 03, 2026 at 03:00:50PM +0000, Ujjal Roy wrote: > Update bridge selftests that configure Max Resp Time (MRT) and Querier > Query Interval (QQI) parameters and validate the resulting Query packet > fields for Max Resp Code (MRC) and Querier Query Interval Code (QQIC). > > These tests cover the IGMPv3 and MLDv2 for below cases: > * MRC and QQIC in linear range. > * MRC and QQIC in non-linear range. > > TEST: Vlan multicast snooping enable [ OK ] > TEST: Vlan mcast_query_interval global option default value [ OK ] > Vlan 10 mcast_query_interval (QQIC) test cases: > TEST: Number of tagged IGMPv2 general query [ OK ] > TEST: IGMPv3 QQIC linear value 60 [ OK ] > TEST: MLDv2 QQIC linear value 60 [ OK ] > TEST: IGMPv3 QQIC non linear value 160 [ OK ] > TEST: MLDv2 QQIC non linear value 160 [ OK ] > TEST: Vlan mcast_query_response_interval global option default value [ OK ] > Vlan 10 mcast_query_response_interval (MRC) test cases: > TEST: IGMPv3 MRC linear value 60 [ OK ] > TEST: IGMPv3 MRC non linear value 160 [ OK ] > TEST: MLDv2 MRC linear value 30000 [ OK ] > TEST: MLDv2 MRC non linear value 60000 [ OK ] > > Signed-off-by: Ujjal Roy <[email protected]> > --- > .../selftests/net/forwarding/.gitignore | 2 + > .../testing/selftests/net/forwarding/Makefile | 10 ++ > .../net/forwarding/bridge_vlan_mcast.sh | 157 +++++++++++++++++- > .../selftests/net/forwarding/mc_decode.c | 73 ++++++++ > .../selftests/net/forwarding/mc_encode.c | 78 +++++++++ > 5 files changed, 315 insertions(+), 5 deletions(-) > create mode 100644 tools/testing/selftests/net/forwarding/mc_decode.c > create mode 100644 tools/testing/selftests/net/forwarding/mc_encode.c > > diff --git a/tools/testing/selftests/net/forwarding/.gitignore b/tools/testing/selftests/net/forwarding/.gitignore > index 418ff96c52ef..aa0c7f1afb4b 100644 > --- a/tools/testing/selftests/net/forwarding/.gitignore > +++ b/tools/testing/selftests/net/forwarding/.gitignore > @@ -1,3 +1,5 @@ > # SPDX-License-Identifier: GPL-2.0-only > forwarding.config > ipmr > +mc_encode Do we really need this binary and all the added complexity? You can just hard code in the test the expected encoded form of the non-linear values > +mc_decode This one doesn't seem to be used > diff --git a/tools/testing/selftests/net/forwarding/Makefile b/tools/testing/selftests/net/forwarding/Makefile > index bbaf4d937dd8..a26da846632d 100644 > --- a/tools/testing/selftests/net/forwarding/Makefile > +++ b/tools/testing/selftests/net/forwarding/Makefile > @@ -1,5 +1,15 @@ > # SPDX-License-Identifier: GPL-2.0+ OR MIT > > +top_srcdir = ../../../../.. > + > +CFLAGS += -Wall -Wl,--no-as-needed -O2 -g -I$(top_srcdir)/usr/include $(KHDR_INCLUDES) > +CFLAGS += -I$(top_srcdir)/tools/include > + > +TEST_GEN_FILES := \ > + mc_encode \ > + mc_decode \ > +# end of TEST_GEN_FILES > + > TEST_PROGS := \ > bridge_activity_notify.sh \ > bridge_fdb_learning_limit.sh \ > diff --git a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh > index 72dfbeaf56b9..89598712f869 100755 > --- a/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh > +++ b/tools/testing/selftests/net/forwarding/bridge_vlan_mcast.sh > @@ -5,6 +5,7 @@ ALL_TESTS="vlmc_control_test vlmc_querier_test vlmc_igmp_mld_version_test \ > vlmc_last_member_test vlmc_startup_query_test vlmc_membership_test \ > vlmc_querier_intvl_test vlmc_query_intvl_test vlmc_query_response_intvl_test \ > vlmc_router_port_test vlmc_filtering_test" > +TEST_NAME="" Why is this needed? Just pass to vlmc_query_get_intvl_match() an argument that indicates if we need MRC / QQIC > NUM_NETIFS=4 > CHECK_TC="yes" > TEST_GROUP="239.10.10.10" > @@ -96,6 +97,17 @@ cleanup() > vrf_cleanup > } > > +check_binary() > +{ > + local cmd=$1; shift > + local args=$@ > + > + if [[ ! -x "$(command -v "$cmd")" ]]; then > + log_test_skip "$args $cmd not found" > + return $EXIT_STATUS > + fi > +} This can be removed as well > + > vlmc_v2join_test() > { > local expect=$1 > @@ -162,14 +174,27 @@ vlmc_query_cnt_setup() > { > local type=$1 > local dev=$2 > + local intvl_match="$3" > > if [[ $type == "igmp" ]]; then > + # This matches: IP Protocol 2 (IGMP) > tc filter add dev $dev egress pref 10 prot 802.1Q \ > flower vlan_id 10 vlan_ethtype ipv4 dst_ip 224.0.0.1 ip_proto 2 \ > + action continue > + # AND Type 0x11 (Query) at offset 24 after IP > + # IP (20 byte IP + 4 bytes Option) > + tc filter add dev $dev egress pref 20 prot 802.1Q u32 \ > + match u8 0x11 0xff at 24 $intvl_match \ > action pass > else > + # This matches: ICMPv6 > tc filter add dev $dev egress pref 10 prot 802.1Q \ > flower vlan_id 10 vlan_ethtype ipv6 dst_ip ff02::1 ip_proto icmpv6 \ > + action continue > + # AND Type 0x82 (Query) at offset 48 after IPv6 > + # IPv6 (40 bytes IPv6 + 2 bytes next HDR + 4 bytes Option + 2 byte pad) > + tc filter add dev $dev egress pref 20 prot 802.1Q u32 \ > + match u8 0x82 0xff at 48 $intvl_match \ > action pass > fi > > @@ -181,9 +206,46 @@ vlmc_query_cnt_cleanup() > local dev=$1 > > ip link set dev br0 type bridge mcast_stats_enabled 0 > + tc filter del dev $dev egress pref 20 > tc filter del dev $dev egress pref 10 > } > > +vlmc_query_get_intvl_match() > +{ > + local type=$1 > + local version=$2 > + local interval=$3 > + local encode="" > + > + if [ "$interval" = "" ]; then > + return > + fi > + > + if [ "$TEST_NAME" = "vlmc_query_intvl_test" ]; then > + # QQIC is 8-bit floating point encoding for IGMPv3 and MLDv2 > + encode="$(./mc_encode 8 $interval)" > + if [ "${type}v${version}" = "igmpv3" ]; then > + # IP 20 bytes + 4 bytes Option + IGMPv3[9] > + echo "match u8 $encode 0xff at 33" > + elif [ "${type}v${version}" = "mldv2" ]; then > + # IPv6 40 + 2 next HDR + 4 Option + 2 pad + MLDv2[25] > + echo "match u8 $encode 0xff at 73" > + fi > + elif [ "$TEST_NAME" = "vlmc_query_response_intvl_test" ]; then > + if [ "${type}v${version}" = "igmpv3" ]; then > + # MRC is 8-bit floating point encoding for MLDv2 > + encode="$(./mc_encode 8 $interval)" > + # IP 20 bytes + 4 bytes Option + IGMPv3[1] > + echo "match u8 $encode 0xff at 25" > + elif [ "${type}v${version}" = "mldv2" ]; then > + # MRC is 16-bit floating point encoding for MLDv2 > + encode="$(./mc_encode 16 $interval)" > + # IPv6 40 + 2 next HDR + 4 Option + 2 pad + MLDv2[4] > + echo "match u16 $encode 0xffff at 52" > + fi > + fi > +} > + > vlmc_check_query() > { > local type=$1 > @@ -191,9 +253,12 @@ vlmc_check_query() > local dev=$3 > local expect=$4 > local time=$5 > + local interval=$6 > + local intvl_match="" > local ret=0 > > - vlmc_query_cnt_setup $type $dev > + intvl_match="$(vlmc_query_get_intvl_match $type $version $interval)" > + vlmc_query_cnt_setup $type $dev "$intvl_match" Did you run shellcheck? I believe it will issue a warning if you don't quote "$type" and "$dev". I realize it differs from other places in the test, but we try to avoid adding new warnings / errors. > > local pre_tx_xstats=$(vlmc_query_cnt_xstats $type $version $dev) > bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_querier 1 > @@ -201,7 +266,7 @@ vlmc_check_query() > if [[ $ret -eq 0 ]]; then > sleep $time > > - local tcstats=$(tc_rule_stats_get $dev 10 egress) > + local tcstats=$(tc_rule_stats_get $dev 20 egress) > local post_tx_xstats=$(vlmc_query_cnt_xstats $type $version $dev) > > if [[ $tcstats != $expect || \ > @@ -428,6 +493,10 @@ vlmc_querier_intvl_test() > > vlmc_query_intvl_test() > { > + TEST_NAME="vlmc_query_intvl_test" > + > + check_binary "./mc_encode" "$TEST_NAME: verify" || return 1 > + > RET=0 > local goutput=`bridge -j vlan global show` > echo -n $goutput | > @@ -440,6 +509,7 @@ vlmc_query_intvl_test() > check_err $? "Wrong default mcast_query_interval global vlan option value" > log_test "Vlan mcast_query_interval global option default value" > > + echo "Vlan 10 mcast_query_interval (QQIC) test cases:" log_info() > RET=0 > bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 0 > bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 200 > @@ -447,14 +517,52 @@ vlmc_query_intvl_test() > # 1 is sent immediately, then 2 more in the next 5 seconds > vlmc_check_query igmp 2 $swp1 3 5 > check_err $? "Wrong number of tagged IGMPv2 general queries sent" > - log_test "Vlan 10 mcast_query_interval option changed to 200" > + log_test "Number of tagged IGMPv2 general query" > + > + RET=0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 3 > + check_err $? "Could not set mcast_igmp_version in vlan 10" > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 2 > + check_err $? "Could not set mcast_mld_version in vlan 10" > + > + RET=0 What's the point in the check_err() above if there is no log_test() before resetting RET? Let's remove them > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 6000 > + check_err $? "Could not set mcast_query_interval in vlan 10" > + # 1 is sent immediately, IGMPv3 QQIC should match with linear value 60s > + vlmc_check_query igmp 3 $swp1 1 1 60 > + check_err $? "Wrong QQIC in sent tagged IGMPv3 general queries" > + log_test "IGMPv3 QQIC linear value 60" > + RET=0 > + # 1 is sent immediately, MLDv2 QQIC should match with linear value 60s > + vlmc_check_query mld 2 $swp1 1 1 60 > + check_err $? "Wrong QQIC in sent tagged MLDv2 general queries" > + log_test "MLDv2 QQIC linear value 60" > > + RET=0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 16000 > + check_err $? "Could not set mcast_query_interval in vlan 10" > + # 1 is sent immediately, IGMPv3 QQIC should match with non linear value 160s > + vlmc_check_query igmp 3 $swp1 1 1 160 > + check_err $? "Wrong QQIC in sent tagged IGMPv3 general queries" > + log_test "IGMPv3 QQIC non linear value 160" > + RET=0 > + # 1 is sent immediately, MLDv2 QQIC should match with non linear value 160s > + vlmc_check_query mld 2 $swp1 1 1 160 > + check_err $? "Wrong QQIC in sent tagged MLDv2 general queries" > + log_test "MLDv2 QQIC non linear value 160" > + > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 2 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 1 > bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 2 > bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_interval 12500 > } > > vlmc_query_response_intvl_test() > { > + TEST_NAME="vlmc_query_response_intvl_test" > + > + check_binary "./mc_encode" "$TEST_NAME: verify" || return 1 > + > RET=0 > local goutput=`bridge -j vlan global show` > echo -n $goutput | > @@ -468,10 +576,49 @@ vlmc_query_response_intvl_test() > log_test "Vlan mcast_query_response_interval global option default value" > > RET=0 > - bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 200 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 3 > + check_err $? "Could not set mcast_igmp_version in vlan 10" Can be removed > + > + echo "Vlan 10 mcast_query_response_interval (MRC) test cases:" log_info() > + RET=0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 600 > + check_err $? "Could not set mcast_query_response_interval in vlan 10" > + # 1 is sent immediately, IGMPv3 MRC should match with linear value 60 units of 1/10s > + vlmc_check_query igmp 3 $swp1 1 1 60 > + check_err $? "Wrong MRC in sent tagged IGMPv3 general queries" > + log_test "IGMPv3 MRC linear value 60" > + > + RET=0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 1600 > + check_err $? "Could not set mcast_query_response_interval in vlan 10" > + # 1 is sent immediately, IGMPv3 MRC should match with non linear value 160 unit of 1/10s > + vlmc_check_query igmp 3 $swp1 1 1 160 > + check_err $? "Wrong MRC in sent tagged IGMPv3 general queries" > + log_test "IGMPv3 MRC non linear value 160" > + > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 2 > + check_err $? "Could not set mcast_mld_version in vlan 10" > + > + RET=0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 3000 > + check_err $? "Could not set mcast_query_response_interval in vlan 10" > + # 1 is sent immediately, MLDv2 MRC should match with linear value 30000(ms) > + vlmc_check_query mld 2 $swp1 1 1 30000 > + check_err $? "Wrong MRC in sent tagged MLDv2 general queries" > + log_test "MLDv2 MRC linear value 30000" > + > + RET=0 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 6000 > check_err $? "Could not set mcast_query_response_interval in vlan 10" > - log_test "Vlan 10 mcast_query_response_interval option changed to 200" > + # 1 is sent immediately, MLDv2 MRC should match with non linear value 60000(ms) > + vlmc_check_query mld 2 $swp1 1 1 60000 > + check_err $? "Wrong MRC in sent tagged MLDv2 general queries" > + log_test "MLDv2 MRC non linear value 60000" > > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_igmp_version 2 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_mld_version 1 > + bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_startup_query_count 2 > bridge vlan global set vid 10 dev br0 mcast_snooping 1 mcast_query_response_interval 1000 > } > > diff --git a/tools/testing/selftests/net/forwarding/mc_decode.c b/tools/testing/selftests/net/forwarding/mc_decode.c > new file mode 100644 > index 000000000000..2c5e784226e4 > --- /dev/null > +++ b/tools/testing/selftests/net/forwarding/mc_decode.c > @@ -0,0 +1,73 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include <stdio.h> > +#include <stdlib.h> > +#include <linux/bitops.h> > + > +/* 8-bit floating-point exponential field decode */ > +#define FP_8BIT_EXP(value) (((value) >> 4) & 0x07) > +#define FP_8BIT_MAN(value) ((value) & 0x0f) > + > +/* 16-bit floating-point exponential field decode */ > +#define FP_16BIT_EXP(value) (((value) >> 12) & 0x0007) > +#define FP_16BIT_MAN(value) ((value) & 0x0fff) > + > +/* 8-bit floating-point exponential field linear threshold */ > +#define FP_8BIT_MIN_THRESHOLD 128 > +/* 8-bit non linear max representable (mant = 0xF, exp = 7) -> 31744 */ > +#define FP_8BIT_MAX_THRESHOLD 31744 > + > +/* 16-bit floating-point exponential field linear threshold */ > +#define FP_16BIT_MIN_THRESHOLD 32768UL > +/* 16-bit non linear max representable (mant = 0xFFF, exp = 7) -> 8387584 */ > +#define FP_16BIT_MAX_THRESHOLD 8387584 > + > +/* This decodes 8-bit floating-point exponential values */ > +static inline uint32_t decode_8bit_field(const u8 code) > +{ > + if (code < FP_8BIT_MIN_THRESHOLD) { > + return code; > + } else { > + uint32_t mc_man, mc_exp; > + > + mc_exp = FP_8BIT_EXP(code); > + mc_man = FP_8BIT_MAN(code); > + return (mc_man | 0x10) << (mc_exp + 3); > + } > +} > + > +/* This decodes 16-bit floating-point exponential values */ > +static inline uint32_t decode_16bit_field(const uint16_t code) > +{ > + if (code < FP_16BIT_MIN_THRESHOLD) { > + return code; > + } else { > + uint32_t mc_man, mc_exp; > + > + mc_exp = FP_16BIT_EXP(code); > + mc_man = FP_16BIT_MAN(code); > + > + return (mc_man | 0x1000) << (mc_exp + 3); > + } > +} > + > +int main(int argc, char *argv[]) > +{ > + uint32_t bits = 8, code = 0, decode = 0; > + > + if (argc != 3) > + return 1; > + > + if (bits != 8 && bits != 16) > + return 1; > + > + bits = atoi(argv[1]); > + code = atoi(argv[2]); > + > + if (bits == 8) > + decode = decode_8bit_field(code); > + else > + decode = decode_16bit_field(code); > + printf("%u\n", decode); > + > + return 0; > +} > diff --git a/tools/testing/selftests/net/forwarding/mc_encode.c b/tools/testing/selftests/net/forwarding/mc_encode.c > new file mode 100644 > index 000000000000..24d9bd9299cc > --- /dev/null > +++ b/tools/testing/selftests/net/forwarding/mc_encode.c > @@ -0,0 +1,78 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#include <stdio.h> > +#include <stdlib.h> > +#include <linux/bitops.h> > + > +/* 8-bit floating-point exponential field linear threshold */ > +#define FP_8BIT_MIN_THRESHOLD 128 > +/* 8-bit non linear max representable (mant = 0xF, exp = 7) -> 31744 */ > +#define FP_8BIT_MAX_THRESHOLD 31744 > + > +/* 16-bit floating-point exponential field linear threshold */ > +#define FP_16BIT_MIN_THRESHOLD 32768UL > +/* 16-bit non linear max representable (mant = 0xFFF, exp = 7) -> 8387584 */ > +#define FP_16BIT_MAX_THRESHOLD 8387584 > + > +/* This encodes value to 8-bit floating-point exponential format */ > +static inline uint8_t encode_8bit_field(unsigned int value) > +{ > + uint8_t mc_exp, mc_man; > + > + /* Value < 128 is literal */ > + if (value < FP_8BIT_MIN_THRESHOLD) > + return value; > + > + /* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */ > + if (value >= FP_8BIT_MAX_THRESHOLD) > + return 0xFF; > + > + mc_exp = fls(value) - 8; > + mc_man = (value >> (mc_exp + 3)) & 0x0F; > + > + return 0x80 | (mc_exp << 4) | mc_man; > +} > + > +/* This encodes value to 16-bit floating-point exponential format */ > +static inline uint16_t encode_16bit_field(unsigned int value) > +{ > + uint16_t mc_man, mc_exp; > + > + /* Value < 32768 is literal */ > + if (value < FP_16BIT_MIN_THRESHOLD) > + return value; > + > + /* Saturate at max representable (mant = 0xFFF, exp = 7) -> 8387584 */ > + if (value >= FP_16BIT_MAX_THRESHOLD) > + return 0xFFFF; > + > + mc_exp = fls(value) - 16; > + mc_man = (value >> (mc_exp + 3)) & 0x0FFF; > + > + return 0x8000 | (mc_exp << 12) | mc_man; > +} > + > +int main(int argc, char *argv[]) > +{ > + unsigned int bits = 8, value = 0; > + uint8_t encoded8 = 0; > + uint16_t encoded16 = 0; > + > + if (argc != 3) > + return 1; > + > + bits = atoi(argv[1]); > + value = atoi(argv[2]); > + > + if (bits != 8 && bits != 16) > + return 1; > + > + if (bits == 8) { > + encoded8 = encode_8bit_field(value); > + printf("%hhu\n", encoded8); > + } else { > + encoded16 = encode_16bit_field(value); > + printf("%hu\n", encoded16); > + } > + > + return 0; > +} > -- > 2.43.0 >