Release 4.11 net/bridge/br_multicast.c
/*
* Bridge multicast support.
*
* Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
*/
#include <linux/err.h>
#include <linux/export.h>
#include <linux/if_ether.h>
#include <linux/igmp.h>
#include <linux/jhash.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/netdevice.h>
#include <linux/netfilter_bridge.h>
#include <linux/random.h>
#include <linux/rculist.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/inetdevice.h>
#include <linux/mroute.h>
#include <net/ip.h>
#include <net/switchdev.h>
#if IS_ENABLED(CONFIG_IPV6)
#include <net/ipv6.h>
#include <net/mld.h>
#include <net/ip6_checksum.h>
#include <net/addrconf.h>
#endif
#include "br_private.h"
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,
struct net_bridge_port *port);
static void br_ip4_multicast_leave_group(struct net_bridge *br,
struct net_bridge_port *port,
__be32 group,
__u16 vid,
const unsigned char *src);
static void __del_port_router(struct net_bridge_port *p);
#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_leave_group(struct net_bridge *br,
struct net_bridge_port *port,
const struct in6_addr *group,
__u16 vid, const unsigned char *src);
#endif
unsigned int br_mdb_rehash_seq;
static inline int br_ip_equal(const struct br_ip *a, const struct br_ip *b)
{
if (a->proto != b->proto)
return 0;
if (a->vid != b->vid)
return 0;
switch (a->proto) {
case htons(ETH_P_IP):
return a->u.ip4 == b->u.ip4;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
return ipv6_addr_equal(&a->u.ip6, &b->u.ip6);
#endif
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hideaki Yoshifuji / 吉藤英明 | 90 | 84.11% | 2 | 40.00% |
Vlad Yasevich | 13 | 12.15% | 1 | 20.00% |
Herbert Xu | 3 | 2.80% | 1 | 20.00% |
Eric Dumazet | 1 | 0.93% | 1 | 20.00% |
Total | 107 | 100.00% | 5 | 100.00% |
static inline int __br_ip4_hash(struct net_bridge_mdb_htable *mdb, __be32 ip,
__u16 vid)
{
return jhash_2words((__force u32)ip, vid, mdb->secret) & (mdb->max - 1);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 27 | 64.29% | 1 | 25.00% |
Vlad Yasevich | 10 | 23.81% | 1 | 25.00% |
Hideaki Yoshifuji / 吉藤英明 | 4 | 9.52% | 1 | 25.00% |
Eric Dumazet | 1 | 2.38% | 1 | 25.00% |
Total | 42 | 100.00% | 4 | 100.00% |
#if IS_ENABLED(CONFIG_IPV6)
static inline int __br_ip6_hash(struct net_bridge_mdb_htable *mdb,
const struct in6_addr *ip,
__u16 vid)
{
return jhash_2words(ipv6_addr_hash(ip), vid,
mdb->secret) & (mdb->max - 1);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hideaki Yoshifuji / 吉藤英明 | 24 | 54.55% | 1 | 33.33% |
Herbert Xu | 12 | 27.27% | 1 | 33.33% |
Vlad Yasevich | 8 | 18.18% | 1 | 33.33% |
Total | 44 | 100.00% | 3 | 100.00% |
#endif
static inline int br_ip_hash(struct net_bridge_mdb_htable *mdb,
struct br_ip *ip)
{
switch (ip->proto) {
case htons(ETH_P_IP):
return __br_ip4_hash(mdb, ip->u.ip4, ip->vid);
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
return __br_ip6_hash(mdb, &ip->u.ip6, ip->vid);
#endif
}
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hideaki Yoshifuji / 吉藤英明 | 72 | 88.89% | 2 | 50.00% |
Vlad Yasevich | 8 | 9.88% | 1 | 25.00% |
Eric Dumazet | 1 | 1.23% | 1 | 25.00% |
Total | 81 | 100.00% | 4 | 100.00% |
static struct net_bridge_mdb_entry *__br_mdb_ip_get(
struct net_bridge_mdb_htable *mdb, struct br_ip *dst, int hash)
{
struct net_bridge_mdb_entry *mp;
hlist_for_each_entry_rcu(mp, &mdb->mhash[hash], hlist[mdb->ver]) {
if (br_ip_equal(&mp->addr, dst))
return mp;
}
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 45 | 83.33% | 2 | 66.67% |
Hideaki Yoshifuji / 吉藤英明 | 9 | 16.67% | 1 | 33.33% |
Total | 54 | 100.00% | 3 | 100.00% |
struct net_bridge_mdb_entry *br_mdb_ip_get(struct net_bridge_mdb_htable *mdb,
struct br_ip *dst)
{
if (!mdb)
return NULL;
return __br_mdb_ip_get(mdb, dst, br_ip_hash(mdb, dst));
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 40 | 100.00% | 1 | 100.00% |
Total | 40 | 100.00% | 1 | 100.00% |
static struct net_bridge_mdb_entry *br_mdb_ip4_get(
struct net_bridge_mdb_htable *mdb, __be32 dst, __u16 vid)
{
struct br_ip br_dst;
br_dst.u.ip4 = dst;
br_dst.proto = htons(ETH_P_IP);
br_dst.vid = vid;
return br_mdb_ip_get(mdb, &br_dst);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hideaki Yoshifuji / 吉藤英明 | 29 | 52.73% | 1 | 20.00% |
Herbert Xu | 17 | 30.91% | 3 | 60.00% |
Vlad Yasevich | 9 | 16.36% | 1 | 20.00% |
Total | 55 | 100.00% | 5 | 100.00% |
#if IS_ENABLED(CONFIG_IPV6)
static struct net_bridge_mdb_entry *br_mdb_ip6_get(
struct net_bridge_mdb_htable *mdb, const struct in6_addr *dst,
__u16 vid)
{
struct br_ip br_dst;
br_dst.u.ip6 = *dst;
br_dst.proto = htons(ETH_P_IPV6);
br_dst.vid = vid;
return br_mdb_ip_get(mdb, &br_dst);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hideaki Yoshifuji / 吉藤英明 | 47 | 79.66% | 1 | 25.00% |
Vlad Yasevich | 9 | 15.25% | 1 | 25.00% |
Alexey Dobriyan | 2 | 3.39% | 1 | 25.00% |
Herbert Xu | 1 | 1.69% | 1 | 25.00% |
Total | 59 | 100.00% | 4 | 100.00% |
#endif
struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
struct sk_buff *skb, u16 vid)
{
struct net_bridge_mdb_htable *mdb = rcu_dereference(br->mdb);
struct br_ip ip;
if (br->multicast_disabled)
return NULL;
if (BR_INPUT_SKB_CB(skb)->igmp)
return NULL;
ip.proto = skb->protocol;
ip.vid = vid;
switch (skb->protocol) {
case htons(ETH_P_IP):
ip.u.ip4 = ip_hdr(skb)->daddr;
break;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
ip.u.ip6 = ipv6_hdr(skb)->daddr;
break;
#endif
default:
return NULL;
}
return br_mdb_ip_get(mdb, &ip);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Hideaki Yoshifuji / 吉藤英明 | 67 | 47.86% | 2 | 28.57% |
Herbert Xu | 59 | 42.14% | 1 | 14.29% |
Américo Wang | 9 | 6.43% | 1 | 14.29% |
Eric Dumazet | 4 | 2.86% | 2 | 28.57% |
Alexey Dobriyan | 1 | 0.71% | 1 | 14.29% |
Total | 140 | 100.00% | 7 | 100.00% |
static void br_mdb_free(struct rcu_head *head)
{
struct net_bridge_mdb_htable *mdb =
container_of(head, struct net_bridge_mdb_htable, rcu);
struct net_bridge_mdb_htable *old = mdb->old;
mdb->old = NULL;
kfree(old->mhash);
kfree(old);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 53 | 100.00% | 1 | 100.00% |
Total | 53 | 100.00% | 1 | 100.00% |
static int br_mdb_copy(struct net_bridge_mdb_htable *new,
struct net_bridge_mdb_htable *old,
int elasticity)
{
struct net_bridge_mdb_entry *mp;
int maxlen;
int len;
int i;
for (i = 0; i < old->max; i++)
hlist_for_each_entry(mp, &old->mhash[i], hlist[old->ver])
hlist_add_head(&mp->hlist[new->ver],
&new->mhash[br_ip_hash(new, &mp->addr)]);
if (!elasticity)
return 0;
maxlen = 0;
for (i = 0; i < new->max; i++) {
len = 0;
hlist_for_each_entry(mp, &new->mhash[i], hlist[new->ver])
len++;
if (len > maxlen)
maxlen = len;
}
return maxlen > elasticity ? -EINVAL : 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 148 | 99.33% | 1 | 50.00% |
Hideaki Yoshifuji / 吉藤英明 | 1 | 0.67% | 1 | 50.00% |
Total | 149 | 100.00% | 2 | 100.00% |
void br_multicast_free_pg(struct rcu_head *head)
{
struct net_bridge_port_group *p =
container_of(head, struct net_bridge_port_group, rcu);
kfree(p);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 30 | 100.00% | 1 | 100.00% |
Total | 30 | 100.00% | 1 | 100.00% |
static void br_multicast_free_group(struct rcu_head *head)
{
struct net_bridge_mdb_entry *mp =
container_of(head, struct net_bridge_mdb_entry, rcu);
kfree(mp);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 31 | 100.00% | 1 | 100.00% |
Total | 31 | 100.00% | 1 | 100.00% |
static void br_multicast_group_expired(unsigned long data)
{
struct net_bridge_mdb_entry *mp = (void *)data;
struct net_bridge *br = mp->br;
struct net_bridge_mdb_htable *mdb;
spin_lock(&br->multicast_lock);
if (!netif_running(br->dev) || timer_pending(&mp->timer))
goto out;
mp->mglist = false;
if (mp->ports)
goto out;
mdb = mlock_dereference(br->mdb, br);
hlist_del_rcu(&mp->hlist[mdb->ver]);
mdb->size--;
call_rcu_bh(&mp->rcu, br_multicast_free_group);
out:
spin_unlock(&br->multicast_lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 123 | 96.09% | 2 | 66.67% |
Eric Dumazet | 5 | 3.91% | 1 | 33.33% |
Total | 128 | 100.00% | 3 | 100.00% |
static void br_multicast_del_pg(struct net_bridge *br,
struct net_bridge_port_group *pg)
{
struct net_bridge_mdb_htable *mdb;
struct net_bridge_mdb_entry *mp;
struct net_bridge_port_group *p;
struct net_bridge_port_group __rcu **pp;
mdb = mlock_dereference(br->mdb, br);
mp = br_mdb_ip_get(mdb, &pg->addr);
if (WARN_ON(!mp))
return;
for (pp = &mp->ports;
(p = mlock_dereference(*pp, br)) != NULL;
pp = &p->next) {
if (p != pg)
continue;
rcu_assign_pointer(*pp, p->next);
hlist_del_init(&p->mglist);
del_timer(&p->timer);
br_mdb_notify(br->dev, p->port, &pg->addr, RTM_DELMDB,
p->flags);
call_rcu_bh(&p->rcu, br_multicast_free_pg);
if (!mp->ports && !mp->mglist &&
netif_running(br->dev))
mod_timer(&mp->timer, jiffies);
return;
}
WARN_ON(1);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 156 | 77.23% | 2 | 28.57% |
Eric Dumazet | 19 | 9.41% | 1 | 14.29% |
Elad Raz | 11 | 5.45% | 1 | 14.29% |
Nikolay Aleksandrov | 11 | 5.45% | 1 | 14.29% |
Stephen Hemminger | 4 | 1.98% | 1 | 14.29% |
Hideaki Yoshifuji / 吉藤英明 | 1 | 0.50% | 1 | 14.29% |
Total | 202 | 100.00% | 7 | 100.00% |
static void br_multicast_port_group_expired(unsigned long data)
{
struct net_bridge_port_group *pg = (void *)data;
struct net_bridge *br = pg->port->br;
spin_lock(&br->multicast_lock);
if (!netif_running(br->dev) || timer_pending(&pg->timer) ||
hlist_unhashed(&pg->mglist) || pg->flags & MDB_PG_FLAGS_PERMANENT)
goto out;
br_multicast_del_pg(br, pg);
out:
spin_unlock(&br->multicast_lock);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 86 | 93.48% | 1 | 33.33% |
Américo Wang | 4 | 4.35% | 1 | 33.33% |
Elad Raz | 2 | 2.17% | 1 | 33.33% |
Total | 92 | 100.00% | 3 | 100.00% |
static int br_mdb_rehash(struct net_bridge_mdb_htable __rcu **mdbp, int max,
int elasticity)
{
struct net_bridge_mdb_htable *old = rcu_dereference_protected(*mdbp, 1);
struct net_bridge_mdb_htable *mdb;
int err;
mdb = kmalloc(sizeof(*mdb), GFP_ATOMIC);
if (!mdb)
return -ENOMEM;
mdb->max = max;
mdb->old = old;
mdb->mhash = kzalloc(max * sizeof(*mdb->mhash), GFP_ATOMIC);
if (!mdb->mhash) {
kfree(mdb);
return -ENOMEM;
}
mdb->size = old ? old->size : 0;
mdb->ver = old ? old->ver ^ 1 : 0;
if (!old || elasticity)
get_random_bytes(&mdb->secret, sizeof(mdb->secret));
else
mdb->secret = old->secret;
if (!old)
goto out;
err = br_mdb_copy(mdb, old, elasticity);
if (err) {
kfree(mdb->mhash);
kfree(mdb);
return err;
}
br_mdb_rehash_seq++;
call_rcu_bh(&mdb->rcu, br_mdb_free);
out:
rcu_assign_pointer(*mdbp, mdb);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 225 | 96.15% | 1 | 33.33% |
Eric Dumazet | 6 | 2.56% | 1 | 33.33% |
Américo Wang | 3 | 1.28% | 1 | 33.33% |
Total | 234 | 100.00% | 3 | 100.00% |
static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
__be32 group,
u8 *igmp_type)
{
struct igmpv3_query *ihv3;
size_t igmp_hdr_size;
struct sk_buff *skb;
struct igmphdr *ih;
struct ethhdr *eth;
struct iphdr *iph;
igmp_hdr_size = sizeof(*ih);
if (br->multicast_igmp_version == 3)
igmp_hdr_size = sizeof(*ihv3);
skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*iph) +
igmp_hdr_size + 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) + igmp_hdr_size + 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);
*igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
switch (br->multicast_igmp_version) {
case 2:
ih = igmp_hdr(skb);
ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
ih->code = (group ? br->multicast_last_member_interval :
br->multicast_query_response_interval) /
(HZ / IGMP_TIMER_SCALE);
ih->group = group;
ih->csum = 0;
ih->csum = ip_compute_csum((void *)ih, sizeof(*ih));
break;
case 3:
ihv3 = igmpv3_query_hdr(skb);
ihv3->type = IGMP_HOST_MEMBERSHIP_QUERY;
ihv3->code = (group ? br->multicast_last_member_interval :
br->multicast_query_response_interval) /
(HZ / IGMP_TIMER_SCALE);
ihv3->group = group;
ihv3->qqic = br->multicast_query_interval / HZ;
ihv3->nsrcs = 0;
ihv3->resv = 0;
ihv3->suppress = 0;
ihv3->qrv = 2;
ihv3->csum = 0;
ihv3->csum = ip_compute_csum((void *)ihv3, sizeof(*ihv3));
break;
}
skb_put(skb, igmp_hdr_size);
__skb_pull(skb, sizeof(*eth));
out:
return skb;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Herbert Xu | 444 | 71.61% | 1 | 16.67% |
Nikolay Aleksandrov | 159 | 25.65% | 2 | 33.33% |
Américo Wang | 15 | 2.42% | 1 | 16.67% |
Joe Perches | 1 | 0.16% | 1 | 16.67% |
Hideaki Yoshifuji / 吉藤英明 | 1 | 0.16% | 1 | 16.67% |
Total | 620 | 100.00% | 6 | 100.00% |
#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)
{
struct mld2_query *mld2q;
unsigned long interval;
struct ipv6hdr *ip6h;
struct mld_msg *mldq;
size_t mld_hdr_size;
struct sk_buff *skb;
struct ethhdr *eth;
u8 *hopopt;
mld_hdr_size = sizeof(*mldq);
if (br->multicast_mld_version == 2)
mld_hdr_size = sizeof(*mld2q);
skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*ip6h) +
8 + mld_hdr_size);
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 + mld_hdr_size);
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);
interval = ipv6_addr_any(grp) ?
br->multicast_query_response_interval :
br->multicast_last_member_interval;
*igmp_type = ICMPV6_MGM_QUERY;
switch (br->multicast_mld_version) {
case 1:
mldq = (struct mld_msg *)icmp6_hdr(skb);
mldq->mld_type = ICMPV6_MGM_QUERY;
mldq->mld_code = 0;
mldq->mld_cksum = 0;
mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval));
mldq->mld_reserved = 0;
mldq->mld_mca = *grp;
mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
sizeof(*mldq), IPPROTO_ICMPV6,
csum_partial(mldq,
sizeof(*mldq),
0));
break;
case 2:
mld2q = (struct mld2_query *)icmp6_hdr(skb);
mld2q->mld2q_mrc = htons((u16)jiffies_to_msecs(interval));
mld2q->mld2q_type = ICMPV6_MGM_QUERY;
mld2q->mld2q_code = 0;
mld2q->mld2q_cksum = 0;
mld2q->mld2q_resv1 = 0;
mld2q->mld2q_resv2 = 0;
mld2q->mld2q_suppress = 0;
mld2q->mld2q_qrv = 2;
mld2q->mld2q_nsrcs = 0;
mld2q->mld2q_qqic = br->multicast_query_interval / HZ;
mld2q->mld2q_mca = *grp;
mld2q->mld2q_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
sizeof(*mld2q),
IPPROTO_ICMPV6,
csum_partial(mld2q,
sizeof(*mld2q),
0));
break;
}
skb_put(skb