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
> 

Reply via email to