Re: [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation

Ido Schimmel
2026-04-07T13:45:46Z

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 <royujjal@gmail.com>
> ---
> .../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
>