]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ipv4: igmp: encode multicast exponential fields
authorUjjal Roy <royujjal@gmail.com>
Sat, 2 May 2026 13:19:04 +0000 (13:19 +0000)
committerJakub Kicinski <kuba@kernel.org>
Tue, 5 May 2026 02:09:41 +0000 (19:09 -0700)
In IGMP, MRC and QQIC fields are not correctly encoded
when generating query packets. Since the receiver of the
query interprets these fields using the IGMPv3 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 MRC and QQIC fields (8-bit) are properly
encoded when transmitting query packets with intervals
that exceed their respective linear threshold value of
128 (for MRT/QQI).

RFC3376: for both MRC and QQIC, values >= 128 represent
the same floating-point encoding as follows:
     0 1 2 3 4 5 6 7
    +-+-+-+-+-+-+-+-+
    |1| exp | mant  |
    +-+-+-+-+-+-+-+-+

Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
Link: https://patch.msgid.link/20260502131907.987-4-royujjal@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
include/linux/igmp.h
net/bridge/br_multicast.c

index 0e095c9f234e9fc08d75e8807d5a6c3feed5b5e1..3a2d35a9f3078f12213f49cba90bd59582c134fc 100644 (file)
@@ -109,6 +109,93 @@ struct ip_mc_list {
 
 /* IGMPv3 floating-point exponential field min threshold */
 #define IGMPV3_EXP_MIN_THRESHOLD       128
+/* IGMPv3 FP max threshold (mant = 0xF, exp = 7) -> 31744 */
+#define IGMPV3_EXP_MAX_THRESHOLD       31744
+
+/* V3 exponential field encoding */
+
+/* IGMPv3 MRC/QQIC 8-bit exponential field encode
+ *
+ * RFC3376, 4.1.1 & 4.1.7. defines only the decoding formula:
+ *     MRT/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 igmpv3_exp_field_encode(unsigned long value)
+{
+       u8 mc_exp, mc_man;
+
+       /* MRC/QQIC < 128 is literal */
+       if (value < IGMPV3_EXP_MIN_THRESHOLD)
+               return value;
+
+       /* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */
+       if (value >= IGMPV3_EXP_MAX_THRESHOLD)
+               return 0xFF;
+
+       mc_exp  = fls(value) - 8;
+       mc_man = (value >> (mc_exp + 3)) & 0x0F;
+
+       return 0x80 | (mc_exp << 4) | mc_man;
+}
+
+/* Calculate Maximum Response Code from Max Resp Time
+ *
+ * RFC3376, relevant sections:
+ *  - 4.1.1. Maximum Response Code
+ *  - 8.3. Query Response Interval
+ *
+ * MRC represents the encoded form of Max Resp Time (MRT); once
+ * decoded, the resulting value is in units of 0.1 seconds (100 ms).
+ */
+static inline u8 igmpv3_mrc(unsigned long mrt)
+{
+       return igmpv3_exp_field_encode(mrt);
+}
+
+/* Calculate Querier's Query Interval Code from Querier's Query Interval
+ *
+ * RFC3376, relevant sections:
+ *  - 4.1.7. QQIC (Querier's Query Interval Code)
+ *  - 8.2. Query Interval
+ *  - 8.12. Older Version Querier Present Timeout
+ *    (the [Query Interval] in the last Query received)
+ *
+ * QQIC represents the encoded form of Querier's Query Interval (QQI);
+ * once decoded, the resulting value is in units of seconds.
+ */
+static inline u8 igmpv3_qqic(unsigned long qi)
+{
+       return igmpv3_exp_field_encode(qi);
+}
 
 /* V3 exponential field decoding */
 
index 1438c023db62bc054adc1c1d89ea6c536f742ae7..27010744d7aeefa7cf1d91d134883ed6a9c2b511 100644 (file)
@@ -934,12 +934,12 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
        size_t pkt_size, igmp_hdr_size;
        unsigned long now = jiffies;
        struct igmpv3_query *ihv3;
+       unsigned long lmqt, mrt;
        void *csum_start = NULL;
        __sum16 *csum = NULL;
        struct sk_buff *skb;
        struct igmphdr *ih;
        struct ethhdr *eth;
-       unsigned long lmqt;
        struct iphdr *iph;
        u16 lmqt_srcs = 0;
 
@@ -1004,15 +1004,15 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
        skb_put(skb, 24);
 
        skb_set_transport_header(skb, skb->len);
+       mrt = group ? brmctx->multicast_last_member_interval :
+                     brmctx->multicast_query_response_interval;
        *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
 
        switch (brmctx->multicast_igmp_version) {
        case 2:
                ih = igmp_hdr(skb);
                ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
-               ih->code = (group ? brmctx->multicast_last_member_interval :
-                                   brmctx->multicast_query_response_interval) /
-                          (HZ / IGMP_TIMER_SCALE);
+               ih->code = mrt / (HZ / IGMP_TIMER_SCALE);
                ih->group = group;
                ih->csum = 0;
                csum = &ih->csum;
@@ -1021,11 +1021,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm
        case 3:
                ihv3 = igmpv3_query_hdr(skb);
                ihv3->type = IGMP_HOST_MEMBERSHIP_QUERY;
-               ihv3->code = (group ? brmctx->multicast_last_member_interval :
-                                     brmctx->multicast_query_response_interval) /
-                            (HZ / IGMP_TIMER_SCALE);
+               ihv3->code = igmpv3_mrc(mrt / (HZ / IGMP_TIMER_SCALE));
                ihv3->group = group;
-               ihv3->qqic = brmctx->multicast_query_interval / HZ;
+               ihv3->qqic = igmpv3_qqic(brmctx->multicast_query_interval / HZ);
                ihv3->nsrcs = htons(lmqt_srcs);
                ihv3->resv = 0;
                ihv3->suppress = sflag;