[RFC ovpn net-next 4/5] ovpn: add IGMPv3 and MLDv2 ASM snooping

Marco Baffo <[email protected]>
Newsgroups gmane.network.openvpn.devel
Message-ID <[email protected]>
Extend multicast snooping to handle IGMPv3 and MLDv2 membership
reports received from tunnel peers. These protocols carry multiple
group records per report, each with an INCLUDE/EXCLUDE filter mode
and an optional source list.

For ASM (Any Source Multicast), the snooping logic treats EXCLUDE
mode with an empty source list as a join and INCLUDE mode with an
empty source list as a leave, matching the behavior of IGMPv2 and
MLDv1. SSM is not supported yet.

Signed-off-by: Marco Baffo <[email protected]>
---
 drivers/net/ovpn/mcast.c | 127 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 125 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ovpn/mcast.c b/drivers/net/ovpn/mcast.c
index 59b0b62afcde..1e436a6721bb 100644
--- a/drivers/net/ovpn/mcast.c
+++ b/drivers/net/ovpn/mcast.c
@@ -268,13 +268,68 @@ static bool ovpn_mcast_mld_offset(struct sk_buff *skb, unsigned int *offsetp)
 	return true;
 }
 
+/**
+ * ovpn_mcast_snoop_mldv2 - inspect an MLDv2 report message
+ * @peer: the peer this packet was received from
+ * @skb: the packet to inspect
+ * @offset: bytes from the start of the network header to the start of the MLD group records
+ * @ngrec: number of group records
+ *
+ * Parse the MLDv2 report and update the multicast subscription table.
+ *
+ * Return: true if the packet was a recognized MLDv2 join/leave and was
+ *         consumed, false otherwise
+ */
+static bool ovpn_mcast_snoop_mldv2(struct ovpn_peer *peer, struct sk_buff *skb,
+				   unsigned int offset, const int ngrec)
+{
+	struct mld2_grec *grec;
+	int i;
+	__u16 nsrcs;
+	unsigned int rec_len;
+
+	for (i = 0; i < ngrec; i++) {
+		if (!pskb_may_pull(skb, offset + sizeof(*grec)))
+			return true;
+
+		grec = (struct mld2_grec *)(skb_network_header(skb) + offset);
+		nsrcs = ntohs(grec->grec_nsrcs);
+
+		rec_len = sizeof(*grec) + nsrcs * sizeof(struct in6_addr) +
+			  grec->grec_auxwords * 4;
+		offset += rec_len;
+
+		if (!pskb_may_pull(skb, offset))
+			return true;
+
+		/* recompute grec after potential head reallocation */
+		grec = (struct mld2_grec *)(skb_network_header(skb) + offset - rec_len);
+
+		/* In MLDv2 ASM, EXCLUDE mode with an empty source list means
+		 * "exclude nothing, receive everything" -> JOIN.
+		 * INCLUDE mode with an empty source list means
+		 * "include nothing, receive nothing" -> LEAVE.
+		 * See RFC 3810, section 4.
+		 */
+		if (nsrcs == 0 &&
+		    (grec->grec_type == MLD2_CHANGE_TO_INCLUDE ||
+		     grec->grec_type == MLD2_MODE_IS_INCLUDE)) {
+			ovpn_mcast_leave(peer->ovpn, peer, &grec->grec_mca);
+		} else {
+			ovpn_mcast_join(peer->ovpn, peer, &grec->grec_mca);
+		}
+	}
+
+	return true;
+}
+
 /**
  * ovpn_mcast_snoop_mld - inspect an IPv6 packet for MLD join/leave messages
  * @peer: the peer this packet was received from
  * @skb: the packet to inspect
  *
  * Parse the MLD header and update the multicast subscription table on
- * MLDv1 reports and done messages.
+ * MLDv1/v2 reports and done messages.
  *
  * Return: true if the packet was a recognized MLD join/leave and was
  *         consumed, false otherwise
@@ -293,6 +348,11 @@ static bool ovpn_mcast_snoop_mld(struct ovpn_peer *peer, struct sk_buff *skb)
 	mld = (struct mld_msg *)(skb_network_header(skb) + offset);
 
 	switch (mld->mld_type) {
+	case ICMPV6_MLD2_REPORT:
+		return ovpn_mcast_snoop_mldv2(peer, skb,
+			offset + sizeof(struct mld2_report),
+			ntohs(((struct mld2_report *)mld)->mld2r_ngrec)
+		);
 	case ICMPV6_MGM_REPORT:
 		ovpn_mcast_join(peer->ovpn, peer, &mld->mld_mca);
 		return true;
@@ -305,13 +365,71 @@ static bool ovpn_mcast_snoop_mld(struct ovpn_peer *peer, struct sk_buff *skb)
 	return false;
 }
 
+/**
+ * ovpn_mcast_snoop_igmpv3 - inspect an IGMPv3 report message
+ * @peer: the peer this packet was received from
+ * @skb: the packet to inspect
+ * @offset: bytes from the start of the network header to the start of the IGMP group records
+ * @ngrec: number of group records
+ *
+ * Parse the IGMPv3 report and update the multicast subscription table.
+ *
+ * Return: true if the packet was a recognized IGMPv3 join/leave and was
+ *         consumed, false otherwise
+ */
+static bool ovpn_mcast_snoop_igmpv3(struct ovpn_peer *peer, struct sk_buff *skb,
+				    unsigned int offset, const int ngrec)
+{
+	struct igmpv3_grec *grec;
+	struct in6_addr addr6;
+	int i;
+	unsigned int rec_len;
+	__u16 nsrcs;
+
+	for (i = 0; i < ngrec; i++) {
+		if (!pskb_may_pull(skb, offset + sizeof(*grec)))
+			return true;
+
+		grec = (struct igmpv3_grec *)(skb_network_header(skb) + offset);
+		nsrcs = ntohs(grec->grec_nsrcs);
+
+		rec_len = sizeof(*grec) + nsrcs * sizeof(__be32) +
+			  grec->grec_auxwords * 4;
+		offset += rec_len;
+
+		if (!pskb_may_pull(skb, offset))
+			return true;
+
+		/* recompute grec after potential head reallocation */
+		grec = (struct igmpv3_grec *)(skb_network_header(skb) + offset - rec_len);
+
+		/* In IGMPv3 ASM, EXCLUDE mode with an empty source list means
+		 * "exclude nothing, receive everything" -> JOIN.
+		 * INCLUDE mode with an empty source list means
+		 * "include nothing, receive nothing" -> LEAVE.
+		 * See RFC 3376, section 3.
+		 */
+		if (nsrcs == 0 &&
+		    (grec->grec_type == IGMPV3_CHANGE_TO_INCLUDE ||
+		     grec->grec_type == IGMPV3_MODE_IS_INCLUDE)) {
+			ipv6_addr_set_v4mapped(grec->grec_mca, &addr6);
+			ovpn_mcast_leave(peer->ovpn, peer, &addr6);
+		} else {
+			ipv6_addr_set_v4mapped(grec->grec_mca, &addr6);
+			ovpn_mcast_join(peer->ovpn, peer, &addr6);
+		}
+	}
+
+	return true;
+}
+
 /**
  * ovpn_mcast_snoop_igmp - inspect an IPv4 packet for IGMP join/leave messages
  * @peer: the peer this packet was received from
  * @skb: the packet to inspect
  *
  * Parse the IGMP header and update the multicast subscription table on
- * IGMPv2 membership reports and leave messages.
+ * IGMPv2/v3 membership reports and leave messages.
  *
  * Return: true if the packet was a recognized IGMP join/leave and was
  *         consumed, false otherwise
@@ -329,6 +447,11 @@ static bool ovpn_mcast_snoop_igmp(struct ovpn_peer *peer, struct sk_buff *skb)
 	ih = (struct igmphdr *)(skb_network_header(skb) + ihl);
 
 	switch (ih->type) {
+	case IGMPV3_HOST_MEMBERSHIP_REPORT:
+		return ovpn_mcast_snoop_igmpv3(peer, skb,
+			ihl + sizeof(struct igmpv3_report),
+			ntohs(((struct igmpv3_report *)ih)->ngrec)
+		);
 	case IGMPV2_HOST_MEMBERSHIP_REPORT:
 		ipv6_addr_set_v4mapped(ih->group, &addr6);
 		ovpn_mcast_join(peer->ovpn, peer, &addr6);
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.