Add bridge IGMPv3 and MLDv2 query support. But before we think it is stable
enough, only enable it when declare in force_igmp/mld_version.

Signed-off-by: Hangbin Liu <liuhang...@gmail.com>
---
 net/bridge/br_multicast.c | 203 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 194 insertions(+), 9 deletions(-)

diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 2136e45..9fb47f3 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -35,6 +35,10 @@
 
 #include "br_private.h"
 
+#define IGMP_V3_SEEN(in_dev) \
+       (IPV4_DEVCONF_ALL(dev_net(in_dev->dev), FORCE_IGMP_VERSION) == 3 || \
+        IN_DEV_CONF_GET((in_dev), FORCE_IGMP_VERSION) == 3)
+
 static void br_multicast_start_querier(struct net_bridge *br,
                                       struct bridge_mcast_own_query *query);
 static void br_multicast_add_router(struct net_bridge *br,
@@ -360,9 +364,8 @@ static int br_mdb_rehash(struct net_bridge_mdb_htable __rcu 
**mdbp, int max,
        return 0;
 }
 
-static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
-                                                   __be32 group,
-                                                   u8 *igmp_type)
+static struct sk_buff *br_ip4_alloc_query_v2(struct net_bridge *br,
+                                            __be32 group, u8 *igmp_type)
 {
        struct sk_buff *skb;
        struct igmphdr *ih;
@@ -428,10 +431,82 @@ static struct sk_buff 
*br_ip4_multicast_alloc_query(struct net_bridge *br,
        return skb;
 }
 
+static struct sk_buff *br_ip4_alloc_query_v3(struct net_bridge *br,
+                                            __be32 group, u8 *igmp_type)
+{
+       struct sk_buff *skb;
+       struct igmpv3_query *ih3;
+       struct ethhdr *eth;
+       struct iphdr *iph;
+
+       skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*iph) +
+                                                sizeof(*ih3) + 4);
+       if (!skb)
+               goto out;
+
+       skb->protocol = htons(ETH_P_IP);
+
+       skb_reset_mac_header(skb);
+       eth = eth_hdr(skb);
+
+       ether_addr_copy(eth->h_source, br->dev->dev_addr);
+       eth->h_dest[0] = 1;
+       eth->h_dest[1] = 0;
+       eth->h_dest[2] = 0x5e;
+       eth->h_dest[3] = 0;
+       eth->h_dest[4] = 0;
+       eth->h_dest[5] = 1;
+       eth->h_proto = htons(ETH_P_IP);
+       skb_put(skb, sizeof(*eth));
+
+       skb_set_network_header(skb, skb->len);
+       iph = ip_hdr(skb);
+
+       iph->version = 4;
+       iph->ihl = 6;
+       iph->tos = 0xc0;
+       iph->tot_len = htons(sizeof(*iph) + sizeof(*ih3) + 4);
+       iph->id = 0;
+       iph->frag_off = htons(IP_DF);
+       iph->ttl = 1;
+       iph->protocol = IPPROTO_IGMP;
+       iph->saddr = br->multicast_query_use_ifaddr ?
+                    inet_select_addr(br->dev, 0, RT_SCOPE_LINK) : 0;
+       iph->daddr = htonl(INADDR_ALLHOSTS_GROUP);
+       ((u8 *)&iph[1])[0] = IPOPT_RA;
+       ((u8 *)&iph[1])[1] = 4;
+       ((u8 *)&iph[1])[2] = 0;
+       ((u8 *)&iph[1])[3] = 0;
+       ip_send_check(iph);
+       skb_put(skb, 24);
+
+       skb_set_transport_header(skb, skb->len);
+       ih3 = igmpv3_query_hdr(skb);
+
+       *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
+       ih3->type = IGMP_HOST_MEMBERSHIP_QUERY;
+       ih3->code = (group ? br->multicast_last_member_interval :
+                           br->multicast_query_response_interval) /
+                  (HZ / IGMP_TIMER_SCALE);
+       ih3->csum = 0;
+       ih3->group = group;
+       ih3->resv = 0;
+       ih3->suppress = 0;
+       ih3->qrv= 2;
+       ih3->qqic = br->multicast_query_interval / HZ;
+       ih3->nsrcs = 0;
+       ih3->csum = ip_compute_csum((void *)ih3, sizeof(struct igmpv3_query ));
+       skb_put(skb, sizeof(*ih3));
+
+       __skb_pull(skb, sizeof(*eth));
+
+out:
+       return skb;
+}
 #if IS_ENABLED(CONFIG_IPV6)
-static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
-                                                   const struct in6_addr *grp,
-                                                   u8 *igmp_type)
+static struct sk_buff *br_ip6_alloc_query_v1(struct net_bridge *br,
+                                            const struct in6_addr *grp,
+                                            u8 *igmp_type)
 {
        struct sk_buff *skb;
        struct ipv6hdr *ip6h;
@@ -514,19 +589,129 @@ static struct sk_buff 
*br_ip6_multicast_alloc_query(struct net_bridge *br,
 out:
        return skb;
 }
+
+static struct sk_buff *br_ip6_alloc_query_v2(struct net_bridge *br,
+                                            const struct in6_addr *grp,
+                                            u8 *igmp_type)
+{
+       struct sk_buff *skb;
+       struct ipv6hdr *ip6h;
+       struct mld2_query *mld2q;
+       struct ethhdr *eth;
+       u8 *hopopt;
+       unsigned long interval;
+
+       skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*ip6h) +
+                                                8 + sizeof(*mld2q));
+       if (!skb)
+               goto out;
+
+       skb->protocol = htons(ETH_P_IPV6);
+
+       /* Ethernet header */
+       skb_reset_mac_header(skb);
+       eth = eth_hdr(skb);
+
+       ether_addr_copy(eth->h_source, br->dev->dev_addr);
+       eth->h_proto = htons(ETH_P_IPV6);
+       skb_put(skb, sizeof(*eth));
+
+       /* IPv6 header + HbH option */
+       skb_set_network_header(skb, skb->len);
+       ip6h = ipv6_hdr(skb);
+
+       *(__force __be32 *)ip6h = htonl(0x60000000);
+       ip6h->payload_len = htons(8 + sizeof(*mld2q));
+       ip6h->nexthdr = IPPROTO_HOPOPTS;
+       ip6h->hop_limit = 1;
+       ipv6_addr_set(&ip6h->daddr, htonl(0xff020000), 0, 0, htonl(1));
+       if (ipv6_dev_get_saddr(dev_net(br->dev), br->dev, &ip6h->daddr, 0,
+                              &ip6h->saddr)) {
+               kfree_skb(skb);
+               br->has_ipv6_addr = 0;
+               return NULL;
+       }
+
+       br->has_ipv6_addr = 1;
+       ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);
+
+       hopopt = (u8 *)(ip6h + 1);
+       hopopt[0] = IPPROTO_ICMPV6;             /* next hdr */
+       hopopt[1] = 0;                          /* length of HbH */
+       hopopt[2] = IPV6_TLV_ROUTERALERT;       /* Router Alert */
+       hopopt[3] = 2;                          /* Length of RA Option */
+       hopopt[4] = 0;                          /* Type = 0x0000 (MLD) */
+       hopopt[5] = 0;
+       hopopt[6] = IPV6_TLV_PAD1;              /* Pad1 */
+       hopopt[7] = IPV6_TLV_PAD1;              /* Pad1 */
+
+       skb_put(skb, sizeof(*ip6h) + 8);
+
+       /* ICMPv6 */
+       skb_set_transport_header(skb, skb->len);
+       mld2q = (struct mld2_query *) icmp6_hdr(skb);
+
+       interval = ipv6_addr_any(grp) ?
+                       br->multicast_query_response_interval :
+                       br->multicast_last_member_interval;
+
+       *igmp_type = ICMPV6_MGM_QUERY;
+       mld2q->mld2q_type = ICMPV6_MGM_QUERY;
+       mld2q->mld2q_code = 0;
+       mld2q->mld2q_cksum = 0;
+       mld2q->mld2q_mrc = htons((u16)jiffies_to_msecs(interval));
+       mld2q->mld2q_resv1 = 0;
+       mld2q->mld2q_mca = *grp;
+       mld2q->mld2q_resv2 = 0;
+       mld2q->mld2q_suppress = 0;
+       mld2q->mld2q_qrv = 2;
+       mld2q->mld2q_qqic = br->multicast_query_interval / HZ;
+       mld2q->mld2q_nsrcs = 0;
+
+       /* checksum */
+       mld2q->mld2q_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
+                                         sizeof(*mld2q), IPPROTO_ICMPV6,
+                                         csum_partial(mld2q,
+                                                      sizeof(*mld2q), 0));
+       skb_put(skb, sizeof(*mld2q));
+
+       __skb_pull(skb, sizeof(*eth));
+
+out:
+       return skb;
+}
 #endif
 
+static int mld_force_mld_version(const struct inet6_dev *idev)
+{
+       if (dev_net(idev->dev)->ipv6.devconf_all->force_mld_version != 0)
+               return dev_net(idev->dev)->ipv6.devconf_all->force_mld_version;
+       else
+               return idev->cnf.force_mld_version;
+}
+
 static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
                                                struct br_ip *addr,
                                                u8 *igmp_type)
 {
+       struct in_device *in_dev = __in_dev_get_rcu(br->dev);
+       struct inet6_dev *idev = __in6_dev_get(br->dev);
        switch (addr->proto) {
        case htons(ETH_P_IP):
-               return br_ip4_multicast_alloc_query(br, addr->u.ip4, igmp_type);
+               if (IGMP_V3_SEEN(in_dev))
+                       return br_ip4_alloc_query_v3(br, addr->u.ip4,
+                                                    igmp_type);
+               else
+                       return br_ip4_alloc_query_v2(br, addr->u.ip4,
+                                                    igmp_type);
 #if IS_ENABLED(CONFIG_IPV6)
        case htons(ETH_P_IPV6):
-               return br_ip6_multicast_alloc_query(br, &addr->u.ip6,
-                                                   igmp_type);
+               if (mld_force_mld_version(idev) == 2)
+                       return br_ip6_alloc_query_v2(br, &addr->u.ip6,
+                                                    igmp_type);
+               else
+                       return br_ip6_alloc_query_v1(br, &addr->u.ip6,
+                                                    igmp_type);
 #endif
        }
        return NULL;
-- 
2.5.5

Reply via email to