* [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding
@ 2026-04-03 15:00 Ujjal Roy
2026-04-03 15:00 ` [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation Ujjal Roy
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Ujjal Roy @ 2026-04-03 15:00 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
Description:
This series addresses a mismatch in how multicast query
intervals and response codes are handled across IPv4 (IGMPv3)
and IPv6 (MLDv2). While decoding logic currently exists,
the corresponding encoding logic is missing during query
packet generation. This leads to incorrect intervals being
transmitted when values exceed their linear thresholds.
The patches introduce a unified floating-point encoding
approach based on RFC3376 and RFC3810, ensuring that large
intervals are correctly represented in QQIC and MRC fields
using the exponent-mantissa format.
Key Changes:
* ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
Removes legacy macros in favor of a cleaner, unified
calculation for retrieving intervals from encoded fields,
improving code maintainability.
* ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
Standardizes MLDv2 terminology by renaming mldv2_mrc()
to mldv2_mrd() (Maximum Response Delay) and introducing
a new API mldv2_qqi for QQI calculation, improving code
readability.
* ipv4: igmp: encode multicast exponential fields
Introduces the logic to dynamically calculate the exponent
and mantissa using bit-scan (fls). This ensures QQIC and
MRC fields (8-bit) are properly encoded when transmitting
query packets with intervals that exceed their respective
linear threshold value of 128 (for QQI/MRT).
* ipv6: mld: encode multicast exponential fields
Applies similar encoding logic for MLDv2. 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).
* selftests: net: bridge: add tests for MRC and QQIC validation
Updates bridge selftests to validate both linear and non-linear
(exponential) encoding for MRC and QQIC fields, ensuring
protocol compliance across IGMPv3 and MLDv2.
Impact:
These changes ensure that multicast queriers and listeners
stay synchronized on timing intervals, preventing protocol
timeouts or premature group membership expiration caused
by incorrectly formatted packet headers.
Test results:
Giving results only from vlmc_query_intvl_test and vlmc_query_response_intvl_test
of the script bridge_vlan_mcast.sh.
Without patchset, here is the result.
- 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 [FAIL]
- Wrong QQIC in sent tagged IGMPv3 general queries
- TEST: MLDv2 QQIC non linear value 160 [FAIL]
- Wrong QQIC in sent tagged MLDv2 general queries
- 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 [FAIL]
- Wrong MRC in sent tagged IGMPv3 general queries
- TEST: MLDv2 MRC linear value 30000 [ OK ]
- TEST: MLDv2 MRC non linear value 60000 [FAIL]
- Wrong MRC in sent tagged MLDv2 general queries
With these patchset, here is the result.
* 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 ]
v3:
- Updated the series title for better clarity; old one is here
https://lore.kernel.org/all/20260326150742.50289-1-royujjal@gmail.com/
- Added key changes section for new selftests patch
- Updated netdev style comments and addressed review comments
- Fixed MLDv2 MRC conversion logic during query generation
- Mentioned intervals in units wherever applicable
- Dropped type casting and fixed indentations
v2:
- Retargeted the series to net-next as suggested
- Fixed a compilation warning in the MLD rename change
- Kept reverse xmas tree order in IGMP exponential encoding change
- Added bridge selftests to validate IGMPv3 Query MRC and QQIC handling
Ujjal Roy (5):
ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
ipv4: igmp: encode multicast exponential fields
ipv6: mld: encode multicast exponential fields
selftests: net: bridge: add tests for MRC and QQIC validation
include/linux/igmp.h | 171 +++++++++++++++-
include/net/mld.h | 190 +++++++++++++++++-
net/bridge/br_multicast.c | 22 +-
net/ipv4/igmp.c | 6 +-
net/ipv6/mcast.c | 19 +-
.../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 +++++++
10 files changed, 675 insertions(+), 53 deletions(-)
create mode 100644 tools/testing/selftests/net/forwarding/mc_decode.c
create mode 100644 tools/testing/selftests/net/forwarding/mc_encode.c
base-commit: 8b0e64d6c9e7feec5ba5643b4fa8b7fd54464778
--
2.43.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
2026-04-03 15:00 [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding Ujjal Roy
@ 2026-04-03 15:00 ` Ujjal Roy
2026-04-07 13:44 ` Ido Schimmel
2026-04-03 15:00 ` [PATCH net-next v3 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi() Ujjal Roy
` (3 subsequent siblings)
4 siblings, 1 reply; 14+ messages in thread
From: Ujjal Roy @ 2026-04-03 15:00 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
Get rid of the IGMPV3_MRC macro and use the igmpv3_mrt() API to
calculate the Max Resp Time from the Maximum Response Code.
Similarly, for IGMPV3_QQIC, use the igmpv3_qqi() API to calculate
the Querier's Query Interval from the QQIC field.
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
include/linux/igmp.h | 84 +++++++++++++++++++++++++++++++++++----
net/bridge/br_multicast.c | 2 +-
net/ipv4/igmp.c | 6 +--
3 files changed, 80 insertions(+), 12 deletions(-)
diff --git a/include/linux/igmp.h b/include/linux/igmp.h
index 073b30a9b850..0624410e75c6 100644
--- a/include/linux/igmp.h
+++ b/include/linux/igmp.h
@@ -92,15 +92,83 @@ struct ip_mc_list {
struct rcu_head rcu;
};
+/* RFC3376, relevant sections:
+ * - 4.1.1. Maximum Response Code
+ * - 4.1.7. QQIC (Querier's Query Interval Code)
+ *
+ * For both MRC and QQIC, values >= 128 use the same floating-point
+ * encoding as follows:
+ *
+ * 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+
+ * |1| exp | mant |
+ * +-+-+-+-+-+-+-+-+
+ */
+#define IGMPV3_FP_EXP(value) (((value) >> 4) & 0x07)
+#define IGMPV3_FP_MAN(value) ((value) & 0x0f)
+
+/* IGMPV3 floating-point exponential field threshold */
+#define IGMPV3_EXP_MIN_THRESHOLD 128
+
/* V3 exponential field decoding */
-#define IGMPV3_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value))
-#define IGMPV3_EXP(thresh, nbmant, nbexp, value) \
- ((value) < (thresh) ? (value) : \
- ((IGMPV3_MASK(value, nbmant) | (1<<(nbmant))) << \
- (IGMPV3_MASK((value) >> (nbmant), nbexp) + (nbexp))))
-
-#define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value)
-#define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value)
+
+/* IGMPv3 QQIC/MRC 8-bit exponential field decode.
+ *
+ * RFC3376, 4.1.1 & 4.1.7. defines the decoding formula:
+ * 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+
+ * |1| exp | mant |
+ * +-+-+-+-+-+-+-+-+
+ * Max Resp Time = (mant | 0x10) << (exp + 3)
+ * QQI = (mant | 0x10) << (exp + 3)
+ */
+static inline unsigned long igmpv3_exp_field_decode(const u8 code)
+{
+ /* RFC3376, relevant sections:
+ * - 4.1.1. Maximum Response Code
+ * - 4.1.7. QQIC (Querier's Query Interval Code)
+ */
+ if (code < IGMPV3_EXP_MIN_THRESHOLD) {
+ return code;
+ } else {
+ unsigned long mc_man, mc_exp;
+
+ mc_exp = IGMPV3_FP_EXP(code);
+ mc_man = IGMPV3_FP_MAN(code);
+
+ return (mc_man | 0x10) << (mc_exp + 3);
+ }
+}
+
+/* Calculate Max Resp Time from Maximum Response Code
+ *
+ * After decode, MRC represents the Maximum Response Time (MRT) in units
+ * of 0.1 seconds (100 ms).
+ */
+static inline unsigned long igmpv3_mrt(const struct igmpv3_query *ih3)
+{
+ /* RFC3376, relevant sections:
+ * - 4.1.1. Maximum Response Code
+ * - 8.3. Query Response Interval
+ */
+ return igmpv3_exp_field_decode(ih3->code);
+}
+
+/* Calculate Querier's Query Interval from Querier's Query Interval Code
+ *
+ * After decode, QQIC represents the Querier's Query Interval in units
+ * of seconds.
+ */
+static inline unsigned long igmpv3_qqi(const struct igmpv3_query *ih3)
+{
+ /* 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)
+ */
+ return igmpv3_exp_field_decode(ih3->qqic);
+}
static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len)
{
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 881d866d687a..9fec76e887bc 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -3518,7 +3518,7 @@ static void br_ip4_multicast_query(struct net_bridge_mcast *brmctx,
goto out;
max_delay = ih3->code ?
- IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE) : 1;
+ igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE) : 1;
} else {
goto out;
}
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
index a674fb44ec25..d7eff36d98c3 100644
--- a/net/ipv4/igmp.c
+++ b/net/ipv4/igmp.c
@@ -991,7 +991,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
* different encoding. We use the v3 encoding as more likely
* to be intended in a v3 query.
*/
- max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
+ max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE);
if (!max_delay)
max_delay = 1; /* can't mod w/ 0 */
} else { /* v3 */
@@ -1006,7 +1006,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
ih3 = igmpv3_query_hdr(skb);
}
- max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
+ max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE);
if (!max_delay)
max_delay = 1; /* can't mod w/ 0 */
WRITE_ONCE(in_dev->mr_maxdelay, max_delay);
@@ -1016,7 +1016,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
* configured value.
*/
in_dev->mr_qrv = ih3->qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
- in_dev->mr_qi = IGMPV3_QQIC(ih3->qqic)*HZ ?: IGMP_QUERY_INTERVAL;
+ in_dev->mr_qi = igmpv3_qqi(ih3) * HZ ? : IGMP_QUERY_INTERVAL;
/* RFC3376, 8.3. Query Response Interval:
* The number of seconds represented by the [Query Response
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v3 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
2026-04-03 15:00 [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding Ujjal Roy
2026-04-03 15:00 ` [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation Ujjal Roy
@ 2026-04-03 15:00 ` Ujjal Roy
2026-04-07 13:44 ` Ido Schimmel
2026-04-03 15:00 ` [PATCH net-next v3 3/5] ipv4: igmp: encode multicast exponential fields Ujjal Roy
` (2 subsequent siblings)
4 siblings, 1 reply; 14+ messages in thread
From: Ujjal Roy @ 2026-04-03 15:00 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
Rename mldv2_mrc() to mldv2_mrd() as it is used to calculate
the Maximum Response Delay from the Maximum Response Code.
Introduce a new API mldv2_qqi() to define the existing
calculation logic of QQI from QQIC. This also organizes
the existing mld_update_qi() API.
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
include/net/mld.h | 70 +++++++++++++++++++++++++++++++++------
net/bridge/br_multicast.c | 2 +-
net/ipv6/mcast.c | 19 ++---------
3 files changed, 64 insertions(+), 27 deletions(-)
diff --git a/include/net/mld.h b/include/net/mld.h
index c07359808493..da3299545ebd 100644
--- a/include/net/mld.h
+++ b/include/net/mld.h
@@ -89,29 +89,79 @@ struct mld2_query {
#define MLDV2_QQIC_EXP(value) (((value) >> 4) & 0x07)
#define MLDV2_QQIC_MAN(value) ((value) & 0x0f)
-#define MLD_EXP_MIN_LIMIT 32768UL
-#define MLDV1_MRD_MAX_COMPAT (MLD_EXP_MIN_LIMIT - 1)
+#define MLD_QQIC_MIN_THRESHOLD 128
+#define MLD_MRC_MIN_THRESHOLD 32768UL
+#define MLDV1_MRD_MAX_COMPAT (MLD_MRC_MIN_THRESHOLD - 1)
#define MLD_MAX_QUEUE 8
#define MLD_MAX_SKBS 32
-static inline unsigned long mldv2_mrc(const struct mld2_query *mlh2)
-{
- /* RFC3810, 5.1.3. Maximum Response Code */
- unsigned long ret, mc_mrc = ntohs(mlh2->mld2q_mrc);
+/* V2 exponential field decoding */
- if (mc_mrc < MLD_EXP_MIN_LIMIT) {
- ret = mc_mrc;
+/* Calculate Maximum Response Delay from Maximum Response Code
+ *
+ * After decode, MRC represents the Maximum Response Delay (MRD) in units
+ * of milliseconds.
+ *
+ * RFC3810, 5.1.3. defines the decoding formula:
+ * 0 1 2 3 4 5 6 7 8 9 A B C D E F
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |1| exp | mant |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * Maximum Response Delay = (mant | 0x1000) << (exp+3)
+ */
+static inline unsigned long mldv2_mrd(const struct mld2_query *mlh2)
+{
+ /* RFC3810, relevant sections:
+ * - 5.1.3. Maximum Response Code
+ * - 9.3. Query Response Interval
+ */
+ unsigned long mc_mrc = ntohs(mlh2->mld2q_mrc);
+
+ if (mc_mrc < MLD_MRC_MIN_THRESHOLD) {
+ return mc_mrc;
} else {
unsigned long mc_man, mc_exp;
mc_exp = MLDV2_MRC_EXP(mc_mrc);
mc_man = MLDV2_MRC_MAN(mc_mrc);
- ret = (mc_man | 0x1000) << (mc_exp + 3);
+ return (mc_man | 0x1000) << (mc_exp + 3);
}
+}
- return ret;
+/* Calculate Querier's Query Interval from Querier's Query Interval Code
+ *
+ * After decode, QQIC represents the Querier's Query Interval in units
+ * of seconds.
+ *
+ * RFC3810, 5.1.9. defines the decoding formula:
+ * 0 1 2 3 4 5 6 7
+ * +-+-+-+-+-+-+-+-+
+ * |1| exp | mant |
+ * +-+-+-+-+-+-+-+-+
+ * QQI = (mant | 0x10) << (exp + 3)
+ */
+static inline unsigned long mldv2_qqi(const struct mld2_query *mlh2)
+{
+ /* RFC3810, relevant sections:
+ * - 5.1.9. QQIC (Querier's Query Interval Code)
+ * - 9.2. Query Interval
+ * - 9.12. Older Version Querier Present Timeout
+ * (the [Query Interval] in the last Query received)
+ */
+ unsigned long qqic = mlh2->mld2q_qqic;
+
+ if (qqic < MLD_QQIC_MIN_THRESHOLD) {
+ return qqic;
+ } else {
+ unsigned long mc_man, mc_exp;
+
+ mc_exp = MLDV2_QQIC_EXP(qqic);
+ mc_man = MLDV2_QQIC_MAN(qqic);
+
+ return (mc_man | 0x10) << (mc_exp + 3);
+ }
}
#endif
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 9fec76e887bc..1438c023db62 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -3606,7 +3606,7 @@ static int br_ip6_multicast_query(struct net_bridge_mcast *brmctx,
mld2q->mld2q_suppress)
goto out;
- max_delay = max(msecs_to_jiffies(mldv2_mrc(mld2q)), 1UL);
+ max_delay = max(msecs_to_jiffies(mldv2_mrd(mld2q)), 1UL);
}
is_general_query = group && ipv6_addr_any(group);
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 3330adcf26db..6ddc18ac59b9 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -1315,20 +1315,7 @@ static void mld_update_qi(struct inet6_dev *idev,
* - 9.12. Older Version Querier Present Timeout
* (the [Query Interval] in the last Query received)
*/
- unsigned long mc_qqi;
-
- if (mlh2->mld2q_qqic < 128) {
- mc_qqi = mlh2->mld2q_qqic;
- } else {
- unsigned long mc_man, mc_exp;
-
- mc_exp = MLDV2_QQIC_EXP(mlh2->mld2q_qqic);
- mc_man = MLDV2_QQIC_MAN(mlh2->mld2q_qqic);
-
- mc_qqi = (mc_man | 0x10) << (mc_exp + 3);
- }
-
- idev->mc_qi = mc_qqi * HZ;
+ idev->mc_qi = mldv2_qqi(mlh2) * HZ;
}
static void mld_update_qri(struct inet6_dev *idev,
@@ -1338,7 +1325,7 @@ static void mld_update_qri(struct inet6_dev *idev,
* - 5.1.3. Maximum Response Code
* - 9.3. Query Response Interval
*/
- idev->mc_qri = msecs_to_jiffies(mldv2_mrc(mlh2));
+ idev->mc_qri = msecs_to_jiffies(mldv2_mrd(mlh2));
}
static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld,
@@ -1390,7 +1377,7 @@ static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld,
static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld,
unsigned long *max_delay)
{
- *max_delay = max(msecs_to_jiffies(mldv2_mrc(mld)), 1UL);
+ *max_delay = max(msecs_to_jiffies(mldv2_mrd(mld)), 1UL);
mld_update_qrv(idev, mld);
mld_update_qi(idev, mld);
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v3 3/5] ipv4: igmp: encode multicast exponential fields
2026-04-03 15:00 [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding Ujjal Roy
2026-04-03 15:00 ` [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation Ujjal Roy
2026-04-03 15:00 ` [PATCH net-next v3 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi() Ujjal Roy
@ 2026-04-03 15:00 ` Ujjal Roy
2026-04-07 13:44 ` Ido Schimmel
2026-04-03 15:00 ` [PATCH net-next v3 4/5] ipv6: mld: " Ujjal Roy
2026-04-03 15:00 ` [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation Ujjal Roy
4 siblings, 1 reply; 14+ messages in thread
From: Ujjal Roy @ 2026-04-03 15:00 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
In IGMP, QQIC and MRC 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 QQIC and MRC fields (8-bit) are properly
encoded when transmitting query packets with intervals
that exceed their respective linear threshold value of
128 (for QQI/MRT).
RFC 3376: if QQIC/MRC >= 128, the QQIC/MRC field represents
a floating-point value as follows:
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|1| exp | mant |
+-+-+-+-+-+-+-+-+
Signed-off-by: Ujjal Roy <royujjal@gmail.com>
---
include/linux/igmp.h | 87 +++++++++++++++++++++++++++++++++++++++
net/bridge/br_multicast.c | 14 +++----
2 files changed, 93 insertions(+), 8 deletions(-)
diff --git a/include/linux/igmp.h b/include/linux/igmp.h
index 0624410e75c6..4e895829cd0e 100644
--- a/include/linux/igmp.h
+++ b/include/linux/igmp.h
@@ -109,6 +109,93 @@ struct ip_mc_list {
/* IGMPV3 floating-point exponential field threshold */
#define IGMPV3_EXP_MIN_THRESHOLD 128
+/* Max representable (mant = 0xF, exp = 7) -> 31744 */
+#define IGMPV3_EXP_MAX_THRESHOLD 31744
+
+/* V3 exponential field encoding */
+
+/* IGMPv3 QQIC/MRC 8-bit exponential field encode.
+ *
+ * RFC3376 defines only the decoding formula:
+ * QQI/MRT = (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;
+
+ /* RFC3376: QQIC/MRC < 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
+ *
+ * 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)
+{
+ /* RFC3376, relevant sections:
+ * - 4.1.1. Maximum Response Code
+ * - 8.3. Query Response Interval
+ */
+ return igmpv3_exp_field_encode(mrt);
+}
+
+/* Calculate Querier's Query Interval Code from Query Interval
+ *
+ * QQIC represents the encoded form of Query Interval (QI); once
+ * decoded, the resulting value is in units of seconds.
+ */
+static inline u8 igmpv3_qqic(unsigned long qi)
+{
+ /* 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)
+ */
+ return igmpv3_exp_field_encode(qi);
+}
/* V3 exponential field decoding */
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 1438c023db62..27010744d7ae 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -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;
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v3 4/5] ipv6: mld: encode multicast exponential fields
2026-04-03 15:00 [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding Ujjal Roy
` (2 preceding siblings ...)
2026-04-03 15:00 ` [PATCH net-next v3 3/5] ipv4: igmp: encode multicast exponential fields Ujjal Roy
@ 2026-04-03 15:00 ` Ujjal Roy
2026-04-07 13:45 ` Ido Schimmel
2026-04-07 14:09 ` Nikolay Aleksandrov
2026-04-03 15:00 ` [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation Ujjal Roy
4 siblings, 2 replies; 14+ messages in thread
From: Ujjal Roy @ 2026-04-03 15:00 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
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)));
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;
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation
2026-04-03 15:00 [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding Ujjal Roy
` (3 preceding siblings ...)
2026-04-03 15:00 ` [PATCH net-next v3 4/5] ipv6: mld: " Ujjal Roy
@ 2026-04-03 15:00 ` Ujjal Roy
2026-04-07 1:25 ` Jakub Kicinski
2026-04-07 13:45 ` Ido Schimmel
4 siblings, 2 replies; 14+ messages in thread
From: Ujjal Roy @ 2026-04-03 15:00 UTC (permalink / raw)
To: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, Ido Schimmel, David Ahern,
Shuah Khan, Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
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
+mc_decode
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=""
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
+}
+
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"
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:"
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
+ 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"
+
+ echo "Vlan 10 mcast_query_response_interval (MRC) test cases:"
+ 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
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation
2026-04-03 15:00 ` [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation Ujjal Roy
@ 2026-04-07 1:25 ` Jakub Kicinski
2026-04-07 6:53 ` Ujjal Roy
2026-04-07 13:45 ` Ido Schimmel
1 sibling, 1 reply; 14+ messages in thread
From: Jakub Kicinski @ 2026-04-07 1:25 UTC (permalink / raw)
To: Ujjal Roy
Cc: David S . Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Nikolay Aleksandrov, Ido Schimmel, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
On Fri, 3 Apr 2026 15:00:50 +0000 Ujjal Roy wrote:
> --- 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
> +mc_decode
please keep this file sorted
> 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
Could you please keep the format more aligned with the ksft
net/Makefile? Also is the -I$(top_srcdir)/tools/include
still necessary? KHDR_INCLUDES don't include $src/usr/include ??
> +TEST_GEN_FILES := \
> + mc_encode \
> + mc_decode \
> +# end of TEST_GEN_FILES
Also needs to be sorted
--
pw-bot: cr
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation
2026-04-07 1:25 ` Jakub Kicinski
@ 2026-04-07 6:53 ` Ujjal Roy
0 siblings, 0 replies; 14+ messages in thread
From: Ujjal Roy @ 2026-04-07 6:53 UTC (permalink / raw)
To: Jakub Kicinski
Cc: David S . Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Nikolay Aleksandrov, Ido Schimmel, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
On Tue, Apr 7, 2026 at 6:55 AM Jakub Kicinski <kuba@kernel.org> wrote:
>
> On Fri, 3 Apr 2026 15:00:50 +0000 Ujjal Roy wrote:
> > --- 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
> > +mc_decode
>
> please keep this file sorted
I will keep all the codes under a single file and keep them sorted.
>
> > 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
>
> Could you please keep the format more aligned with the ksft
> net/Makefile? Also is the -I$(top_srcdir)/tools/include
> still necessary? KHDR_INCLUDES don't include $src/usr/include ??
>
> > +TEST_GEN_FILES := \
> > + mc_encode \
> > + mc_decode \
> > +# end of TEST_GEN_FILES
>
> Also needs to be sorted
> --
> pw-bot: cr
I need bitops.h for fls() API, so I can see KHDR_INCLUDES is not
needed but tools/include is needed. I don't see lib.mk is including
the tools/include.
@Nikolay Aleksandrov Please review the updated test cases in this patch.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation
2026-04-03 15:00 ` [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation Ujjal Roy
@ 2026-04-07 13:44 ` Ido Schimmel
0 siblings, 0 replies; 14+ messages in thread
From: Ido Schimmel @ 2026-04-07 13:44 UTC (permalink / raw)
To: Ujjal Roy
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
On Fri, Apr 03, 2026 at 03:00:46PM +0000, Ujjal Roy wrote:
> Get rid of the IGMPV3_MRC macro and use the igmpv3_mrt() API to
> calculate the Max Resp Time from the Maximum Response Code.
>
> Similarly, for IGMPV3_QQIC, use the igmpv3_qqi() API to calculate
> the Querier's Query Interval from the QQIC field.
>
> Signed-off-by: Ujjal Roy <royujjal@gmail.com>
Looks fine, but see a few nits below
> ---
> include/linux/igmp.h | 84 +++++++++++++++++++++++++++++++++++----
> net/bridge/br_multicast.c | 2 +-
> net/ipv4/igmp.c | 6 +--
> 3 files changed, 80 insertions(+), 12 deletions(-)
>
> diff --git a/include/linux/igmp.h b/include/linux/igmp.h
> index 073b30a9b850..0624410e75c6 100644
> --- a/include/linux/igmp.h
> +++ b/include/linux/igmp.h
> @@ -92,15 +92,83 @@ struct ip_mc_list {
> struct rcu_head rcu;
> };
>
> +/* RFC3376, relevant sections:
> + * - 4.1.1. Maximum Response Code
> + * - 4.1.7. QQIC (Querier's Query Interval Code)
> + *
> + * For both MRC and QQIC, values >= 128 use the same floating-point
> + * encoding as follows:
> + *
> + * 0 1 2 3 4 5 6 7
> + * +-+-+-+-+-+-+-+-+
> + * |1| exp | mant |
> + * +-+-+-+-+-+-+-+-+
> + */
> +#define IGMPV3_FP_EXP(value) (((value) >> 4) & 0x07)
> +#define IGMPV3_FP_MAN(value) ((value) & 0x0f)
> +
> +/* IGMPV3 floating-point exponential field threshold */
> +#define IGMPV3_EXP_MIN_THRESHOLD 128
> +
> /* V3 exponential field decoding */
> -#define IGMPV3_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value))
> -#define IGMPV3_EXP(thresh, nbmant, nbexp, value) \
> - ((value) < (thresh) ? (value) : \
> - ((IGMPV3_MASK(value, nbmant) | (1<<(nbmant))) << \
> - (IGMPV3_MASK((value) >> (nbmant), nbexp) + (nbexp))))
> -
> -#define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value)
> -#define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value)
> +
> +/* IGMPv3 QQIC/MRC 8-bit exponential field decode.
> + *
> + * RFC3376, 4.1.1 & 4.1.7. defines the decoding formula:
> + * 0 1 2 3 4 5 6 7
> + * +-+-+-+-+-+-+-+-+
> + * |1| exp | mant |
> + * +-+-+-+-+-+-+-+-+
> + * Max Resp Time = (mant | 0x10) << (exp + 3)
> + * QQI = (mant | 0x10) << (exp + 3)
> + */
> +static inline unsigned long igmpv3_exp_field_decode(const u8 code)
> +{
> + /* RFC3376, relevant sections:
> + * - 4.1.1. Maximum Response Code
> + * - 4.1.7. QQIC (Querier's Query Interval Code)
> + */
I find it weird to have a comment at the beginning of the function when
there's already a comment above the function. Let's remove this comment
since this information is already present in the MRC/QQIC functions
below.
> + if (code < IGMPV3_EXP_MIN_THRESHOLD) {
> + return code;
> + } else {
> + unsigned long mc_man, mc_exp;
> +
> + mc_exp = IGMPV3_FP_EXP(code);
> + mc_man = IGMPV3_FP_MAN(code);
> +
> + return (mc_man | 0x10) << (mc_exp + 3);
> + }
> +}
> +
> +/* Calculate Max Resp Time from Maximum Response Code
> + *
> + * After decode, MRC represents the Maximum Response Time (MRT) in units
> + * of 0.1 seconds (100 ms).
> + */
> +static inline unsigned long igmpv3_mrt(const struct igmpv3_query *ih3)
> +{
> + /* RFC3376, relevant sections:
> + * - 4.1.1. Maximum Response Code
> + * - 8.3. Query Response Interval
> + */
Please move this to the comment above the function
> + return igmpv3_exp_field_decode(ih3->code);
> +}
> +
> +/* Calculate Querier's Query Interval from Querier's Query Interval Code
> + *
> + * After decode, QQIC represents the Querier's Query Interval in units
> + * of seconds.
> + */
> +static inline unsigned long igmpv3_qqi(const struct igmpv3_query *ih3)
> +{
> + /* 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)
> + */
Likewise
> + return igmpv3_exp_field_decode(ih3->qqic);
> +}
>
> static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len)
> {
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 881d866d687a..9fec76e887bc 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -3518,7 +3518,7 @@ static void br_ip4_multicast_query(struct net_bridge_mcast *brmctx,
> goto out;
>
> max_delay = ih3->code ?
> - IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE) : 1;
> + igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE) : 1;
> } else {
> goto out;
> }
> diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c
> index a674fb44ec25..d7eff36d98c3 100644
> --- a/net/ipv4/igmp.c
> +++ b/net/ipv4/igmp.c
> @@ -991,7 +991,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
> * different encoding. We use the v3 encoding as more likely
> * to be intended in a v3 query.
> */
> - max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
> + max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE);
> if (!max_delay)
> max_delay = 1; /* can't mod w/ 0 */
> } else { /* v3 */
> @@ -1006,7 +1006,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
> ih3 = igmpv3_query_hdr(skb);
> }
>
> - max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
> + max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE);
> if (!max_delay)
> max_delay = 1; /* can't mod w/ 0 */
> WRITE_ONCE(in_dev->mr_maxdelay, max_delay);
> @@ -1016,7 +1016,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb,
> * configured value.
> */
> in_dev->mr_qrv = ih3->qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv);
> - in_dev->mr_qi = IGMPV3_QQIC(ih3->qqic)*HZ ?: IGMP_QUERY_INTERVAL;
> + in_dev->mr_qi = igmpv3_qqi(ih3) * HZ ? : IGMP_QUERY_INTERVAL;
>
> /* RFC3376, 8.3. Query Response Interval:
> * The number of seconds represented by the [Query Response
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi()
2026-04-03 15:00 ` [PATCH net-next v3 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi() Ujjal Roy
@ 2026-04-07 13:44 ` Ido Schimmel
0 siblings, 0 replies; 14+ messages in thread
From: Ido Schimmel @ 2026-04-07 13:44 UTC (permalink / raw)
To: Ujjal Roy
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
On Fri, Apr 03, 2026 at 03:00:47PM +0000, Ujjal Roy wrote:
> Rename mldv2_mrc() to mldv2_mrd() as it is used to calculate
> the Maximum Response Delay from the Maximum Response Code.
>
> Introduce a new API mldv2_qqi() to define the existing
> calculation logic of QQI from QQIC. This also organizes
> the existing mld_update_qi() API.
>
> Signed-off-by: Ujjal Roy <royujjal@gmail.com>
> ---
> include/net/mld.h | 70 +++++++++++++++++++++++++++++++++------
> net/bridge/br_multicast.c | 2 +-
> net/ipv6/mcast.c | 19 ++---------
> 3 files changed, 64 insertions(+), 27 deletions(-)
Again, code looks fine, but same comments from the previous patch apply
here
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 3/5] ipv4: igmp: encode multicast exponential fields
2026-04-03 15:00 ` [PATCH net-next v3 3/5] ipv4: igmp: encode multicast exponential fields Ujjal Roy
@ 2026-04-07 13:44 ` Ido Schimmel
0 siblings, 0 replies; 14+ messages in thread
From: Ido Schimmel @ 2026-04-07 13:44 UTC (permalink / raw)
To: Ujjal Roy
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
On Fri, Apr 03, 2026 at 03:00:48PM +0000, Ujjal Roy wrote:
> In IGMP, QQIC and MRC 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 QQIC and MRC fields (8-bit) are properly
> encoded when transmitting query packets with intervals
> that exceed their respective linear threshold value of
> 128 (for QQI/MRT).
>
> RFC 3376: if QQIC/MRC >= 128, the QQIC/MRC field represents
> a floating-point value as follows:
> 0 1 2 3 4 5 6 7
> +-+-+-+-+-+-+-+-+
> |1| exp | mant |
> +-+-+-+-+-+-+-+-+
>
> Signed-off-by: Ujjal Roy <royujjal@gmail.com>
Same comments as on the previous patches, but otherwise LGTM
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 4/5] ipv6: mld: encode multicast exponential fields
2026-04-03 15:00 ` [PATCH net-next v3 4/5] ipv6: mld: " Ujjal Roy
@ 2026-04-07 13:45 ` Ido Schimmel
2026-04-07 14:09 ` Nikolay Aleksandrov
1 sibling, 0 replies; 14+ messages in thread
From: Ido Schimmel @ 2026-04-07 13:45 UTC (permalink / raw)
To: Ujjal Roy
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
On Fri, Apr 03, 2026 at 03:00:49PM +0000, 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>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation
2026-04-03 15:00 ` [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation Ujjal Roy
2026-04-07 1:25 ` Jakub Kicinski
@ 2026-04-07 13:45 ` Ido Schimmel
1 sibling, 0 replies; 14+ messages in thread
From: Ido Schimmel @ 2026-04-07 13:45 UTC (permalink / raw)
To: Ujjal Roy
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Nikolay Aleksandrov, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata, Ujjal Roy, bridge, netdev,
linux-kernel, linux-kselftest
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
>
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH net-next v3 4/5] ipv6: mld: encode multicast exponential fields
2026-04-03 15:00 ` [PATCH net-next v3 4/5] ipv6: mld: " Ujjal Roy
2026-04-07 13:45 ` Ido Schimmel
@ 2026-04-07 14:09 ` Nikolay Aleksandrov
1 sibling, 0 replies; 14+ messages in thread
From: Nikolay Aleksandrov @ 2026-04-07 14:09 UTC (permalink / raw)
To: Ujjal Roy, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Ido Schimmel, David Ahern, Shuah Khan,
Andy Roulin, Yong Wang, Petr Machata
Cc: Ujjal Roy, bridge, netdev, linux-kernel, linux-kselftest
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;
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-04-07 14:09 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03 15:00 [PATCH net-next v3 0/5] net: bridge: mcast: support exponential field encoding Ujjal Roy
2026-04-03 15:00 ` [PATCH net-next v3 1/5] ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation Ujjal Roy
2026-04-07 13:44 ` Ido Schimmel
2026-04-03 15:00 ` [PATCH net-next v3 2/5] ipv6: mld: rename mldv2_mrc() and add mldv2_qqi() Ujjal Roy
2026-04-07 13:44 ` Ido Schimmel
2026-04-03 15:00 ` [PATCH net-next v3 3/5] ipv4: igmp: encode multicast exponential fields Ujjal Roy
2026-04-07 13:44 ` Ido Schimmel
2026-04-03 15:00 ` [PATCH net-next v3 4/5] ipv6: mld: " Ujjal Roy
2026-04-07 13:45 ` Ido Schimmel
2026-04-07 14:09 ` Nikolay Aleksandrov
2026-04-03 15:00 ` [PATCH net-next v3 5/5] selftests: net: bridge: add tests for MRC and QQIC validation Ujjal Roy
2026-04-07 1:25 ` Jakub Kicinski
2026-04-07 6:53 ` Ujjal Roy
2026-04-07 13:45 ` Ido Schimmel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox