Re: [PATCH net-next v3 4/5] ipv6: mld: encode multicast exponential fields

Nikolay Aleksandrov
2026-04-07T14:09:06Z

On 03/04/2026 18:00, Ujjal Roy wrote:
> In MLD, QQIC and MRC fields are not correctly encoded when
> generating query packets. Since the receiver of the query
> interprets these fields using the MLDv2 floating-point
> decoding logic, any value that exceeds the linear threshold
> is incorrectly parsed as an exponential value, leading to
> an incorrect interval calculation.
>
> Encode and assign the corresponding protocol fields during
> query generation. Introduce the logic to dynamically
> calculate the exponent and mantissa using bit-scan (fls).
> This ensures QQIC (8-bit) and MRC (16-bit) fields are
> properly encoded when transmitting query packets with
> intervals that exceed their respective linear thresholds
> (128 for QQI; 32768 for MRD).
>
> RFC3810: If QQIC >= 128, the QQIC field represents a
> floating-point value as follows:
> 0 1 2 3 4 5 6 7
> +-+-+-+-+-+-+-+-+
> |1| exp | mant |
> +-+-+-+-+-+-+-+-+
>
> RFC3810: If Maximum Response Code >= 32768, the Maximum
> Response Code field represents a floating-point value as
> follows:
> 0 1 2 3 4 5 6 7 8 9 A B C D E F
> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> |1| exp | mant |
> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
>
> Signed-off-by: Ujjal Roy <royujjal@gmail.com>
> ---
> include/net/mld.h | 122 ++++++++++++++++++++++++++++++++++++++
> net/bridge/br_multicast.c | 4 +-
> 2 files changed, 124 insertions(+), 2 deletions(-)
>
> diff --git a/include/net/mld.h b/include/net/mld.h
> index da3299545ebd..147e8c44eb28 100644
> --- a/include/net/mld.h
> +++ b/include/net/mld.h
> @@ -90,12 +90,134 @@ struct mld2_query {
> #define MLDV2_QQIC_MAN(value) ((value) & 0x0f)
>
> #define MLD_QQIC_MIN_THRESHOLD 128
> +/* Max representable (mant = 0xF, exp = 7) -> 31744 */
> +#define MLD_QQIC_MAX_THRESHOLD 31744
> #define MLD_MRC_MIN_THRESHOLD 32768UL
> +/* Max representable (mant = 0xFFF, exp = 7) -> 8387584 */
> +#define MLD_MRC_MAX_THRESHOLD 8387584
> #define MLDV1_MRD_MAX_COMPAT (MLD_MRC_MIN_THRESHOLD - 1)
>
> #define MLD_MAX_QUEUE 8
> #define MLD_MAX_SKBS 32
>
> +/* V2 exponential field encoding */
> +
> +/*
> + * Calculate Maximum Response Code from Maximum Response Delay
> + *
> + * MRC represents the 16-bit encoded form of Maximum Response
> + * Delay (MRD); once decoded, the resulting value is in
> + * milliseconds.
> + *
> + * RFC3810 defines only the decoding formula:
> + * Maximum Response Delay = (mant | 0x1000) << (exp + 3)
> + *
> + * but does NOT define the encoding procedure. To derive exponent:
> + *
> + * For the 16-bit MRC, the "hidden bit" (0x1000) is left shifted by 12
> + * to sit above the 12-bit mantissa. The RFC then shifts this entire
> + * block left by (exp + 3) to reconstruct the value.
> + * So, 'hidden bit' is the MSB which is shifted by (12 + exp + 3).
> + *
> + * Total left shift of the hidden bit = 12 + (exp + 3) = exp + 15.
> + * This is the MSB at the 0-based bit position: (exp + 15).
> + * Since fls() is 1-based, fls(value) - 1 = exp + 15.
> + *
> + * Therefore:
> + * exp = fls(value) - 16
> + * mant = (value >> (exp + 3)) & 0x0FFF
> + *
> + * Final encoding formula:
> + * 0x8000 | (exp << 12) | mant
> + *
> + * Example (value = 1311744):
> + * 0 1 2 3
> + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> + * |0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0| 1311744
> + * | ^-^--------mant---------^ ^...(exp+3)...^| exp=5
> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> + *
> + * Encoded:
> + * 0x8000 | (5 << 12) | 0x404 = 0xD404
> + */
> +static inline u16 mldv2_mrc(unsigned long mrd)
> +{
> + u16 mc_man, mc_exp;
> +
> + /* RFC3810: MRC < 32768 is literal */
> + if (mrd < MLD_MRC_MIN_THRESHOLD)
> + return mrd;
> +
> + /* Saturate at max representable (mant = 0xFFF, exp = 7) -> 8387584 */
> + if (mrd >= MLD_MRC_MAX_THRESHOLD)
> + return 0xFFFF;
> +
> + mc_exp = fls(mrd) - 16;
> + mc_man = (mrd >> (mc_exp + 3)) & 0x0FFF;
> +
> + return 0x8000 | (mc_exp << 12) | mc_man;
> +}
> +
> +/*
> + * Calculate Querier's Query Interval Code from Query Interval
> + *
> + * QQIC represents the 8-bit encoded form of Query Interval (QQI);
> + * once decoded, the resulting value is in seconds.
> + *
> + * MLDv2 QQIC 8-bit floating-point encoding (RFC3810).
> + *
> + * RFC3810 defines only the decoding formula:
> + * QQI = (mant | 0x10) << (exp + 3)
> + *
> + * but does NOT define the encoding procedure. To derive exponent:
> + *
> + * For any value of mantissa and exponent, the decoding formula
> + * indicates that the "hidden bit" (0x10) is shifted 4 bits left
> + * to sit above the 4-bit mantissa. The RFC again shifts this
> + * entire block left by (exp + 3) to reconstruct the value.
> + * So, 'hidden bit' is the MSB which is shifted by (4 + exp + 3).
> + *
> + * Total left shift of the 'hidden bit' = 4 + (exp + 3) = exp + 7.
> + * This is the MSB at the 0-based bit position: (exp + 7).
> + * Since fls() is 1-based, fls(value) - 1 = exp + 7.
> + *
> + * Therefore:
> + * exp = fls(value) - 8
> + * mant = (value >> (exp + 3)) & 0x0F
> + *
> + * Final encoding formula:
> + * 0x80 | (exp << 4) | mant
> + *
> + * Example (value = 3200):
> + * 0 1
> + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> + * |0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0| (value = 3200)
> + * | ^-^-mant^ ^..(exp+3)..^| exp = 4, mant = 9
> + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
> + *
> + * Encoded:
> + * 0x80 | (4 << 4) | 9 = 0xC9
> + */
> +static inline u8 mldv2_qqic(unsigned long value)
> +{
> + u8 mc_man, mc_exp;
> +
> + /* RFC3810: QQIC < 128 is literal */
> + if (value < MLD_QQIC_MIN_THRESHOLD)
> + return value;
> +
> + /* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */
> + if (value >= MLD_QQIC_MAX_THRESHOLD)
> + return 0xFF;
> +
> + mc_exp = fls(value) - 8;
> + mc_man = (value >> (mc_exp + 3)) & 0x0F;
> +
> + return 0x80 | (mc_exp << 4) | mc_man;
> +}
> +
> /* V2 exponential field decoding */
>
> /* Calculate Maximum Response Delay from Maximum Response Code
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 27010744d7ae..a22e44c4fa48 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -1181,7 +1181,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
> break;
> case 2:
> mld2q = (struct mld2_query *)icmp6_hdr(skb);
> - mld2q->mld2q_mrc = htons((u16)jiffies_to_msecs(interval));
> + mld2q->mld2q_mrc = htons((u16)mldv2_mrc(jiffies_to_msecs(interval)));

you've defined mldv2_mrc as u16, no need to cast it again here

> mld2q->mld2q_type = ICMPV6_MGM_QUERY;
> mld2q->mld2q_code = 0;
> mld2q->mld2q_cksum = 0;
> @@ -1190,7 +1190,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm
> mld2q->mld2q_suppress = sflag;
> mld2q->mld2q_qrv = 2;
> mld2q->mld2q_nsrcs = htons(llqt_srcs);
> - mld2q->mld2q_qqic = brmctx->multicast_query_interval / HZ;
> + mld2q->mld2q_qqic = mldv2_qqic(brmctx->multicast_query_interval / HZ);
> mld2q->mld2q_mca = *group;
> csum = &mld2q->mld2q_cksum;
> csum_start = (void *)mld2q;