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