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 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel
