162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/err.h>
362306a36Sopenharmony_ci#include <linux/igmp.h>
462306a36Sopenharmony_ci#include <linux/kernel.h>
562306a36Sopenharmony_ci#include <linux/netdevice.h>
662306a36Sopenharmony_ci#include <linux/rculist.h>
762306a36Sopenharmony_ci#include <linux/skbuff.h>
862306a36Sopenharmony_ci#include <linux/if_ether.h>
962306a36Sopenharmony_ci#include <net/ip.h>
1062306a36Sopenharmony_ci#include <net/netlink.h>
1162306a36Sopenharmony_ci#include <net/switchdev.h>
1262306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
1362306a36Sopenharmony_ci#include <net/ipv6.h>
1462306a36Sopenharmony_ci#include <net/addrconf.h>
1562306a36Sopenharmony_ci#endif
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include "br_private.h"
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_cistatic bool
2062306a36Sopenharmony_cibr_ip4_rports_get_timer(struct net_bridge_mcast_port *pmctx,
2162306a36Sopenharmony_ci			unsigned long *timer)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	*timer = br_timer_value(&pmctx->ip4_mc_router_timer);
2462306a36Sopenharmony_ci	return !hlist_unhashed(&pmctx->ip4_rlist);
2562306a36Sopenharmony_ci}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic bool
2862306a36Sopenharmony_cibr_ip6_rports_get_timer(struct net_bridge_mcast_port *pmctx,
2962306a36Sopenharmony_ci			unsigned long *timer)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
3262306a36Sopenharmony_ci	*timer = br_timer_value(&pmctx->ip6_mc_router_timer);
3362306a36Sopenharmony_ci	return !hlist_unhashed(&pmctx->ip6_rlist);
3462306a36Sopenharmony_ci#else
3562306a36Sopenharmony_ci	*timer = 0;
3662306a36Sopenharmony_ci	return false;
3762306a36Sopenharmony_ci#endif
3862306a36Sopenharmony_ci}
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic size_t __br_rports_one_size(void)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	return nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PORT */
4362306a36Sopenharmony_ci	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_TIMER */
4462306a36Sopenharmony_ci	       nla_total_size(sizeof(u8)) +  /* MDBA_ROUTER_PATTR_TYPE */
4562306a36Sopenharmony_ci	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET_TIMER */
4662306a36Sopenharmony_ci	       nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET6_TIMER */
4762306a36Sopenharmony_ci	       nla_total_size(sizeof(u32));  /* MDBA_ROUTER_PATTR_VID */
4862306a36Sopenharmony_ci}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cisize_t br_rports_size(const struct net_bridge_mcast *brmctx)
5162306a36Sopenharmony_ci{
5262306a36Sopenharmony_ci	struct net_bridge_mcast_port *pmctx;
5362306a36Sopenharmony_ci	size_t size = nla_total_size(0); /* MDBA_ROUTER */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	rcu_read_lock();
5662306a36Sopenharmony_ci	hlist_for_each_entry_rcu(pmctx, &brmctx->ip4_mc_router_list,
5762306a36Sopenharmony_ci				 ip4_rlist)
5862306a36Sopenharmony_ci		size += __br_rports_one_size();
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
6162306a36Sopenharmony_ci	hlist_for_each_entry_rcu(pmctx, &brmctx->ip6_mc_router_list,
6262306a36Sopenharmony_ci				 ip6_rlist)
6362306a36Sopenharmony_ci		size += __br_rports_one_size();
6462306a36Sopenharmony_ci#endif
6562306a36Sopenharmony_ci	rcu_read_unlock();
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return size;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ciint br_rports_fill_info(struct sk_buff *skb,
7162306a36Sopenharmony_ci			const struct net_bridge_mcast *brmctx)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	u16 vid = brmctx->vlan ? brmctx->vlan->vid : 0;
7462306a36Sopenharmony_ci	bool have_ip4_mc_rtr, have_ip6_mc_rtr;
7562306a36Sopenharmony_ci	unsigned long ip4_timer, ip6_timer;
7662306a36Sopenharmony_ci	struct nlattr *nest, *port_nest;
7762306a36Sopenharmony_ci	struct net_bridge_port *p;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	if (!brmctx->multicast_router || !br_rports_have_mc_router(brmctx))
8062306a36Sopenharmony_ci		return 0;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, MDBA_ROUTER);
8362306a36Sopenharmony_ci	if (nest == NULL)
8462306a36Sopenharmony_ci		return -EMSGSIZE;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	list_for_each_entry_rcu(p, &brmctx->br->port_list, list) {
8762306a36Sopenharmony_ci		struct net_bridge_mcast_port *pmctx;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		if (vid) {
9062306a36Sopenharmony_ci			struct net_bridge_vlan *v;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci			v = br_vlan_find(nbp_vlan_group(p), vid);
9362306a36Sopenharmony_ci			if (!v)
9462306a36Sopenharmony_ci				continue;
9562306a36Sopenharmony_ci			pmctx = &v->port_mcast_ctx;
9662306a36Sopenharmony_ci		} else {
9762306a36Sopenharmony_ci			pmctx = &p->multicast_ctx;
9862306a36Sopenharmony_ci		}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci		have_ip4_mc_rtr = br_ip4_rports_get_timer(pmctx, &ip4_timer);
10162306a36Sopenharmony_ci		have_ip6_mc_rtr = br_ip6_rports_get_timer(pmctx, &ip6_timer);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci		if (!have_ip4_mc_rtr && !have_ip6_mc_rtr)
10462306a36Sopenharmony_ci			continue;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		port_nest = nla_nest_start_noflag(skb, MDBA_ROUTER_PORT);
10762306a36Sopenharmony_ci		if (!port_nest)
10862306a36Sopenharmony_ci			goto fail;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		if (nla_put_nohdr(skb, sizeof(u32), &p->dev->ifindex) ||
11162306a36Sopenharmony_ci		    nla_put_u32(skb, MDBA_ROUTER_PATTR_TIMER,
11262306a36Sopenharmony_ci				max(ip4_timer, ip6_timer)) ||
11362306a36Sopenharmony_ci		    nla_put_u8(skb, MDBA_ROUTER_PATTR_TYPE,
11462306a36Sopenharmony_ci			       p->multicast_ctx.multicast_router) ||
11562306a36Sopenharmony_ci		    (have_ip4_mc_rtr &&
11662306a36Sopenharmony_ci		     nla_put_u32(skb, MDBA_ROUTER_PATTR_INET_TIMER,
11762306a36Sopenharmony_ci				 ip4_timer)) ||
11862306a36Sopenharmony_ci		    (have_ip6_mc_rtr &&
11962306a36Sopenharmony_ci		     nla_put_u32(skb, MDBA_ROUTER_PATTR_INET6_TIMER,
12062306a36Sopenharmony_ci				 ip6_timer)) ||
12162306a36Sopenharmony_ci		    (vid && nla_put_u16(skb, MDBA_ROUTER_PATTR_VID, vid))) {
12262306a36Sopenharmony_ci			nla_nest_cancel(skb, port_nest);
12362306a36Sopenharmony_ci			goto fail;
12462306a36Sopenharmony_ci		}
12562306a36Sopenharmony_ci		nla_nest_end(skb, port_nest);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	nla_nest_end(skb, nest);
12962306a36Sopenharmony_ci	return 0;
13062306a36Sopenharmony_cifail:
13162306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
13262306a36Sopenharmony_ci	return -EMSGSIZE;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic void __mdb_entry_fill_flags(struct br_mdb_entry *e, unsigned char flags)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	e->state = flags & MDB_PG_FLAGS_PERMANENT;
13862306a36Sopenharmony_ci	e->flags = 0;
13962306a36Sopenharmony_ci	if (flags & MDB_PG_FLAGS_OFFLOAD)
14062306a36Sopenharmony_ci		e->flags |= MDB_FLAGS_OFFLOAD;
14162306a36Sopenharmony_ci	if (flags & MDB_PG_FLAGS_FAST_LEAVE)
14262306a36Sopenharmony_ci		e->flags |= MDB_FLAGS_FAST_LEAVE;
14362306a36Sopenharmony_ci	if (flags & MDB_PG_FLAGS_STAR_EXCL)
14462306a36Sopenharmony_ci		e->flags |= MDB_FLAGS_STAR_EXCL;
14562306a36Sopenharmony_ci	if (flags & MDB_PG_FLAGS_BLOCKED)
14662306a36Sopenharmony_ci		e->flags |= MDB_FLAGS_BLOCKED;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip,
15062306a36Sopenharmony_ci				 struct nlattr **mdb_attrs)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	memset(ip, 0, sizeof(struct br_ip));
15362306a36Sopenharmony_ci	ip->vid = entry->vid;
15462306a36Sopenharmony_ci	ip->proto = entry->addr.proto;
15562306a36Sopenharmony_ci	switch (ip->proto) {
15662306a36Sopenharmony_ci	case htons(ETH_P_IP):
15762306a36Sopenharmony_ci		ip->dst.ip4 = entry->addr.u.ip4;
15862306a36Sopenharmony_ci		if (mdb_attrs && mdb_attrs[MDBE_ATTR_SOURCE])
15962306a36Sopenharmony_ci			ip->src.ip4 = nla_get_in_addr(mdb_attrs[MDBE_ATTR_SOURCE]);
16062306a36Sopenharmony_ci		break;
16162306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
16262306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
16362306a36Sopenharmony_ci		ip->dst.ip6 = entry->addr.u.ip6;
16462306a36Sopenharmony_ci		if (mdb_attrs && mdb_attrs[MDBE_ATTR_SOURCE])
16562306a36Sopenharmony_ci			ip->src.ip6 = nla_get_in6_addr(mdb_attrs[MDBE_ATTR_SOURCE]);
16662306a36Sopenharmony_ci		break;
16762306a36Sopenharmony_ci#endif
16862306a36Sopenharmony_ci	default:
16962306a36Sopenharmony_ci		ether_addr_copy(ip->dst.mac_addr, entry->addr.u.mac_addr);
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int __mdb_fill_srcs(struct sk_buff *skb,
17562306a36Sopenharmony_ci			   struct net_bridge_port_group *p)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	struct net_bridge_group_src *ent;
17862306a36Sopenharmony_ci	struct nlattr *nest, *nest_ent;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (hlist_empty(&p->src_list))
18162306a36Sopenharmony_ci		return 0;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	nest = nla_nest_start(skb, MDBA_MDB_EATTR_SRC_LIST);
18462306a36Sopenharmony_ci	if (!nest)
18562306a36Sopenharmony_ci		return -EMSGSIZE;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	hlist_for_each_entry_rcu(ent, &p->src_list, node,
18862306a36Sopenharmony_ci				 lockdep_is_held(&p->key.port->br->multicast_lock)) {
18962306a36Sopenharmony_ci		nest_ent = nla_nest_start(skb, MDBA_MDB_SRCLIST_ENTRY);
19062306a36Sopenharmony_ci		if (!nest_ent)
19162306a36Sopenharmony_ci			goto out_cancel_err;
19262306a36Sopenharmony_ci		switch (ent->addr.proto) {
19362306a36Sopenharmony_ci		case htons(ETH_P_IP):
19462306a36Sopenharmony_ci			if (nla_put_in_addr(skb, MDBA_MDB_SRCATTR_ADDRESS,
19562306a36Sopenharmony_ci					    ent->addr.src.ip4)) {
19662306a36Sopenharmony_ci				nla_nest_cancel(skb, nest_ent);
19762306a36Sopenharmony_ci				goto out_cancel_err;
19862306a36Sopenharmony_ci			}
19962306a36Sopenharmony_ci			break;
20062306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
20162306a36Sopenharmony_ci		case htons(ETH_P_IPV6):
20262306a36Sopenharmony_ci			if (nla_put_in6_addr(skb, MDBA_MDB_SRCATTR_ADDRESS,
20362306a36Sopenharmony_ci					     &ent->addr.src.ip6)) {
20462306a36Sopenharmony_ci				nla_nest_cancel(skb, nest_ent);
20562306a36Sopenharmony_ci				goto out_cancel_err;
20662306a36Sopenharmony_ci			}
20762306a36Sopenharmony_ci			break;
20862306a36Sopenharmony_ci#endif
20962306a36Sopenharmony_ci		default:
21062306a36Sopenharmony_ci			nla_nest_cancel(skb, nest_ent);
21162306a36Sopenharmony_ci			continue;
21262306a36Sopenharmony_ci		}
21362306a36Sopenharmony_ci		if (nla_put_u32(skb, MDBA_MDB_SRCATTR_TIMER,
21462306a36Sopenharmony_ci				br_timer_value(&ent->timer))) {
21562306a36Sopenharmony_ci			nla_nest_cancel(skb, nest_ent);
21662306a36Sopenharmony_ci			goto out_cancel_err;
21762306a36Sopenharmony_ci		}
21862306a36Sopenharmony_ci		nla_nest_end(skb, nest_ent);
21962306a36Sopenharmony_ci	}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	nla_nest_end(skb, nest);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	return 0;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ciout_cancel_err:
22662306a36Sopenharmony_ci	nla_nest_cancel(skb, nest);
22762306a36Sopenharmony_ci	return -EMSGSIZE;
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic int __mdb_fill_info(struct sk_buff *skb,
23162306a36Sopenharmony_ci			   struct net_bridge_mdb_entry *mp,
23262306a36Sopenharmony_ci			   struct net_bridge_port_group *p)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	bool dump_srcs_mode = false;
23562306a36Sopenharmony_ci	struct timer_list *mtimer;
23662306a36Sopenharmony_ci	struct nlattr *nest_ent;
23762306a36Sopenharmony_ci	struct br_mdb_entry e;
23862306a36Sopenharmony_ci	u8 flags = 0;
23962306a36Sopenharmony_ci	int ifindex;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	memset(&e, 0, sizeof(e));
24262306a36Sopenharmony_ci	if (p) {
24362306a36Sopenharmony_ci		ifindex = p->key.port->dev->ifindex;
24462306a36Sopenharmony_ci		mtimer = &p->timer;
24562306a36Sopenharmony_ci		flags = p->flags;
24662306a36Sopenharmony_ci	} else {
24762306a36Sopenharmony_ci		ifindex = mp->br->dev->ifindex;
24862306a36Sopenharmony_ci		mtimer = &mp->timer;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	__mdb_entry_fill_flags(&e, flags);
25262306a36Sopenharmony_ci	e.ifindex = ifindex;
25362306a36Sopenharmony_ci	e.vid = mp->addr.vid;
25462306a36Sopenharmony_ci	if (mp->addr.proto == htons(ETH_P_IP)) {
25562306a36Sopenharmony_ci		e.addr.u.ip4 = mp->addr.dst.ip4;
25662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
25762306a36Sopenharmony_ci	} else if (mp->addr.proto == htons(ETH_P_IPV6)) {
25862306a36Sopenharmony_ci		e.addr.u.ip6 = mp->addr.dst.ip6;
25962306a36Sopenharmony_ci#endif
26062306a36Sopenharmony_ci	} else {
26162306a36Sopenharmony_ci		ether_addr_copy(e.addr.u.mac_addr, mp->addr.dst.mac_addr);
26262306a36Sopenharmony_ci		e.state = MDB_PERMANENT;
26362306a36Sopenharmony_ci	}
26462306a36Sopenharmony_ci	e.addr.proto = mp->addr.proto;
26562306a36Sopenharmony_ci	nest_ent = nla_nest_start_noflag(skb,
26662306a36Sopenharmony_ci					 MDBA_MDB_ENTRY_INFO);
26762306a36Sopenharmony_ci	if (!nest_ent)
26862306a36Sopenharmony_ci		return -EMSGSIZE;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (nla_put_nohdr(skb, sizeof(e), &e) ||
27162306a36Sopenharmony_ci	    nla_put_u32(skb,
27262306a36Sopenharmony_ci			MDBA_MDB_EATTR_TIMER,
27362306a36Sopenharmony_ci			br_timer_value(mtimer)))
27462306a36Sopenharmony_ci		goto nest_err;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	switch (mp->addr.proto) {
27762306a36Sopenharmony_ci	case htons(ETH_P_IP):
27862306a36Sopenharmony_ci		dump_srcs_mode = !!(mp->br->multicast_ctx.multicast_igmp_version == 3);
27962306a36Sopenharmony_ci		if (mp->addr.src.ip4) {
28062306a36Sopenharmony_ci			if (nla_put_in_addr(skb, MDBA_MDB_EATTR_SOURCE,
28162306a36Sopenharmony_ci					    mp->addr.src.ip4))
28262306a36Sopenharmony_ci				goto nest_err;
28362306a36Sopenharmony_ci			break;
28462306a36Sopenharmony_ci		}
28562306a36Sopenharmony_ci		break;
28662306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
28762306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
28862306a36Sopenharmony_ci		dump_srcs_mode = !!(mp->br->multicast_ctx.multicast_mld_version == 2);
28962306a36Sopenharmony_ci		if (!ipv6_addr_any(&mp->addr.src.ip6)) {
29062306a36Sopenharmony_ci			if (nla_put_in6_addr(skb, MDBA_MDB_EATTR_SOURCE,
29162306a36Sopenharmony_ci					     &mp->addr.src.ip6))
29262306a36Sopenharmony_ci				goto nest_err;
29362306a36Sopenharmony_ci			break;
29462306a36Sopenharmony_ci		}
29562306a36Sopenharmony_ci		break;
29662306a36Sopenharmony_ci#endif
29762306a36Sopenharmony_ci	default:
29862306a36Sopenharmony_ci		ether_addr_copy(e.addr.u.mac_addr, mp->addr.dst.mac_addr);
29962306a36Sopenharmony_ci	}
30062306a36Sopenharmony_ci	if (p) {
30162306a36Sopenharmony_ci		if (nla_put_u8(skb, MDBA_MDB_EATTR_RTPROT, p->rt_protocol))
30262306a36Sopenharmony_ci			goto nest_err;
30362306a36Sopenharmony_ci		if (dump_srcs_mode &&
30462306a36Sopenharmony_ci		    (__mdb_fill_srcs(skb, p) ||
30562306a36Sopenharmony_ci		     nla_put_u8(skb, MDBA_MDB_EATTR_GROUP_MODE,
30662306a36Sopenharmony_ci				p->filter_mode)))
30762306a36Sopenharmony_ci			goto nest_err;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci	nla_nest_end(skb, nest_ent);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	return 0;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_cinest_err:
31462306a36Sopenharmony_ci	nla_nest_cancel(skb, nest_ent);
31562306a36Sopenharmony_ci	return -EMSGSIZE;
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
31962306a36Sopenharmony_ci			    struct net_device *dev)
32062306a36Sopenharmony_ci{
32162306a36Sopenharmony_ci	int idx = 0, s_idx = cb->args[1], err = 0, pidx = 0, s_pidx = cb->args[2];
32262306a36Sopenharmony_ci	struct net_bridge *br = netdev_priv(dev);
32362306a36Sopenharmony_ci	struct net_bridge_mdb_entry *mp;
32462306a36Sopenharmony_ci	struct nlattr *nest, *nest2;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	if (!br_opt_get(br, BROPT_MULTICAST_ENABLED))
32762306a36Sopenharmony_ci		return 0;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, MDBA_MDB);
33062306a36Sopenharmony_ci	if (nest == NULL)
33162306a36Sopenharmony_ci		return -EMSGSIZE;
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	hlist_for_each_entry_rcu(mp, &br->mdb_list, mdb_node) {
33462306a36Sopenharmony_ci		struct net_bridge_port_group *p;
33562306a36Sopenharmony_ci		struct net_bridge_port_group __rcu **pp;
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci		if (idx < s_idx)
33862306a36Sopenharmony_ci			goto skip;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci		nest2 = nla_nest_start_noflag(skb, MDBA_MDB_ENTRY);
34162306a36Sopenharmony_ci		if (!nest2) {
34262306a36Sopenharmony_ci			err = -EMSGSIZE;
34362306a36Sopenharmony_ci			break;
34462306a36Sopenharmony_ci		}
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci		if (!s_pidx && mp->host_joined) {
34762306a36Sopenharmony_ci			err = __mdb_fill_info(skb, mp, NULL);
34862306a36Sopenharmony_ci			if (err) {
34962306a36Sopenharmony_ci				nla_nest_cancel(skb, nest2);
35062306a36Sopenharmony_ci				break;
35162306a36Sopenharmony_ci			}
35262306a36Sopenharmony_ci		}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci		for (pp = &mp->ports; (p = rcu_dereference(*pp)) != NULL;
35562306a36Sopenharmony_ci		      pp = &p->next) {
35662306a36Sopenharmony_ci			if (!p->key.port)
35762306a36Sopenharmony_ci				continue;
35862306a36Sopenharmony_ci			if (pidx < s_pidx)
35962306a36Sopenharmony_ci				goto skip_pg;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci			err = __mdb_fill_info(skb, mp, p);
36262306a36Sopenharmony_ci			if (err) {
36362306a36Sopenharmony_ci				nla_nest_end(skb, nest2);
36462306a36Sopenharmony_ci				goto out;
36562306a36Sopenharmony_ci			}
36662306a36Sopenharmony_ciskip_pg:
36762306a36Sopenharmony_ci			pidx++;
36862306a36Sopenharmony_ci		}
36962306a36Sopenharmony_ci		pidx = 0;
37062306a36Sopenharmony_ci		s_pidx = 0;
37162306a36Sopenharmony_ci		nla_nest_end(skb, nest2);
37262306a36Sopenharmony_ciskip:
37362306a36Sopenharmony_ci		idx++;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ciout:
37762306a36Sopenharmony_ci	cb->args[1] = idx;
37862306a36Sopenharmony_ci	cb->args[2] = pidx;
37962306a36Sopenharmony_ci	nla_nest_end(skb, nest);
38062306a36Sopenharmony_ci	return err;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ciint br_mdb_dump(struct net_device *dev, struct sk_buff *skb,
38462306a36Sopenharmony_ci		struct netlink_callback *cb)
38562306a36Sopenharmony_ci{
38662306a36Sopenharmony_ci	struct net_bridge *br = netdev_priv(dev);
38762306a36Sopenharmony_ci	struct br_port_msg *bpm;
38862306a36Sopenharmony_ci	struct nlmsghdr *nlh;
38962306a36Sopenharmony_ci	int err;
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid,
39262306a36Sopenharmony_ci			cb->nlh->nlmsg_seq, RTM_GETMDB, sizeof(*bpm),
39362306a36Sopenharmony_ci			NLM_F_MULTI);
39462306a36Sopenharmony_ci	if (!nlh)
39562306a36Sopenharmony_ci		return -EMSGSIZE;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	bpm = nlmsg_data(nlh);
39862306a36Sopenharmony_ci	memset(bpm, 0, sizeof(*bpm));
39962306a36Sopenharmony_ci	bpm->ifindex = dev->ifindex;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	rcu_read_lock();
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	err = br_mdb_fill_info(skb, cb, dev);
40462306a36Sopenharmony_ci	if (err)
40562306a36Sopenharmony_ci		goto out;
40662306a36Sopenharmony_ci	err = br_rports_fill_info(skb, &br->multicast_ctx);
40762306a36Sopenharmony_ci	if (err)
40862306a36Sopenharmony_ci		goto out;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ciout:
41162306a36Sopenharmony_ci	rcu_read_unlock();
41262306a36Sopenharmony_ci	nlmsg_end(skb, nlh);
41362306a36Sopenharmony_ci	return err;
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistatic int nlmsg_populate_mdb_fill(struct sk_buff *skb,
41762306a36Sopenharmony_ci				   struct net_device *dev,
41862306a36Sopenharmony_ci				   struct net_bridge_mdb_entry *mp,
41962306a36Sopenharmony_ci				   struct net_bridge_port_group *pg,
42062306a36Sopenharmony_ci				   int type)
42162306a36Sopenharmony_ci{
42262306a36Sopenharmony_ci	struct nlmsghdr *nlh;
42362306a36Sopenharmony_ci	struct br_port_msg *bpm;
42462306a36Sopenharmony_ci	struct nlattr *nest, *nest2;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	nlh = nlmsg_put(skb, 0, 0, type, sizeof(*bpm), 0);
42762306a36Sopenharmony_ci	if (!nlh)
42862306a36Sopenharmony_ci		return -EMSGSIZE;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	bpm = nlmsg_data(nlh);
43162306a36Sopenharmony_ci	memset(bpm, 0, sizeof(*bpm));
43262306a36Sopenharmony_ci	bpm->family  = AF_BRIDGE;
43362306a36Sopenharmony_ci	bpm->ifindex = dev->ifindex;
43462306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, MDBA_MDB);
43562306a36Sopenharmony_ci	if (nest == NULL)
43662306a36Sopenharmony_ci		goto cancel;
43762306a36Sopenharmony_ci	nest2 = nla_nest_start_noflag(skb, MDBA_MDB_ENTRY);
43862306a36Sopenharmony_ci	if (nest2 == NULL)
43962306a36Sopenharmony_ci		goto end;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	if (__mdb_fill_info(skb, mp, pg))
44262306a36Sopenharmony_ci		goto end;
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	nla_nest_end(skb, nest2);
44562306a36Sopenharmony_ci	nla_nest_end(skb, nest);
44662306a36Sopenharmony_ci	nlmsg_end(skb, nlh);
44762306a36Sopenharmony_ci	return 0;
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ciend:
45062306a36Sopenharmony_ci	nla_nest_end(skb, nest);
45162306a36Sopenharmony_cicancel:
45262306a36Sopenharmony_ci	nlmsg_cancel(skb, nlh);
45362306a36Sopenharmony_ci	return -EMSGSIZE;
45462306a36Sopenharmony_ci}
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_cistatic size_t rtnl_mdb_nlmsg_size(struct net_bridge_port_group *pg)
45762306a36Sopenharmony_ci{
45862306a36Sopenharmony_ci	size_t nlmsg_size = NLMSG_ALIGN(sizeof(struct br_port_msg)) +
45962306a36Sopenharmony_ci			    nla_total_size(sizeof(struct br_mdb_entry)) +
46062306a36Sopenharmony_ci			    nla_total_size(sizeof(u32));
46162306a36Sopenharmony_ci	struct net_bridge_group_src *ent;
46262306a36Sopenharmony_ci	size_t addr_size = 0;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (!pg)
46562306a36Sopenharmony_ci		goto out;
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	/* MDBA_MDB_EATTR_RTPROT */
46862306a36Sopenharmony_ci	nlmsg_size += nla_total_size(sizeof(u8));
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	switch (pg->key.addr.proto) {
47162306a36Sopenharmony_ci	case htons(ETH_P_IP):
47262306a36Sopenharmony_ci		/* MDBA_MDB_EATTR_SOURCE */
47362306a36Sopenharmony_ci		if (pg->key.addr.src.ip4)
47462306a36Sopenharmony_ci			nlmsg_size += nla_total_size(sizeof(__be32));
47562306a36Sopenharmony_ci		if (pg->key.port->br->multicast_ctx.multicast_igmp_version == 2)
47662306a36Sopenharmony_ci			goto out;
47762306a36Sopenharmony_ci		addr_size = sizeof(__be32);
47862306a36Sopenharmony_ci		break;
47962306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
48062306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
48162306a36Sopenharmony_ci		/* MDBA_MDB_EATTR_SOURCE */
48262306a36Sopenharmony_ci		if (!ipv6_addr_any(&pg->key.addr.src.ip6))
48362306a36Sopenharmony_ci			nlmsg_size += nla_total_size(sizeof(struct in6_addr));
48462306a36Sopenharmony_ci		if (pg->key.port->br->multicast_ctx.multicast_mld_version == 1)
48562306a36Sopenharmony_ci			goto out;
48662306a36Sopenharmony_ci		addr_size = sizeof(struct in6_addr);
48762306a36Sopenharmony_ci		break;
48862306a36Sopenharmony_ci#endif
48962306a36Sopenharmony_ci	}
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	/* MDBA_MDB_EATTR_GROUP_MODE */
49262306a36Sopenharmony_ci	nlmsg_size += nla_total_size(sizeof(u8));
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	/* MDBA_MDB_EATTR_SRC_LIST nested attr */
49562306a36Sopenharmony_ci	if (!hlist_empty(&pg->src_list))
49662306a36Sopenharmony_ci		nlmsg_size += nla_total_size(0);
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	hlist_for_each_entry(ent, &pg->src_list, node) {
49962306a36Sopenharmony_ci		/* MDBA_MDB_SRCLIST_ENTRY nested attr +
50062306a36Sopenharmony_ci		 * MDBA_MDB_SRCATTR_ADDRESS + MDBA_MDB_SRCATTR_TIMER
50162306a36Sopenharmony_ci		 */
50262306a36Sopenharmony_ci		nlmsg_size += nla_total_size(0) +
50362306a36Sopenharmony_ci			      nla_total_size(addr_size) +
50462306a36Sopenharmony_ci			      nla_total_size(sizeof(u32));
50562306a36Sopenharmony_ci	}
50662306a36Sopenharmony_ciout:
50762306a36Sopenharmony_ci	return nlmsg_size;
50862306a36Sopenharmony_ci}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_civoid br_mdb_notify(struct net_device *dev,
51162306a36Sopenharmony_ci		   struct net_bridge_mdb_entry *mp,
51262306a36Sopenharmony_ci		   struct net_bridge_port_group *pg,
51362306a36Sopenharmony_ci		   int type)
51462306a36Sopenharmony_ci{
51562306a36Sopenharmony_ci	struct net *net = dev_net(dev);
51662306a36Sopenharmony_ci	struct sk_buff *skb;
51762306a36Sopenharmony_ci	int err = -ENOBUFS;
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_ci	br_switchdev_mdb_notify(dev, mp, pg, type);
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	skb = nlmsg_new(rtnl_mdb_nlmsg_size(pg), GFP_ATOMIC);
52262306a36Sopenharmony_ci	if (!skb)
52362306a36Sopenharmony_ci		goto errout;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	err = nlmsg_populate_mdb_fill(skb, dev, mp, pg, type);
52662306a36Sopenharmony_ci	if (err < 0) {
52762306a36Sopenharmony_ci		kfree_skb(skb);
52862306a36Sopenharmony_ci		goto errout;
52962306a36Sopenharmony_ci	}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	rtnl_notify(skb, net, 0, RTNLGRP_MDB, NULL, GFP_ATOMIC);
53262306a36Sopenharmony_ci	return;
53362306a36Sopenharmony_cierrout:
53462306a36Sopenharmony_ci	rtnl_set_sk_err(net, RTNLGRP_MDB, err);
53562306a36Sopenharmony_ci}
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_cistatic int nlmsg_populate_rtr_fill(struct sk_buff *skb,
53862306a36Sopenharmony_ci				   struct net_device *dev,
53962306a36Sopenharmony_ci				   int ifindex, u16 vid, u32 pid,
54062306a36Sopenharmony_ci				   u32 seq, int type, unsigned int flags)
54162306a36Sopenharmony_ci{
54262306a36Sopenharmony_ci	struct nlattr *nest, *port_nest;
54362306a36Sopenharmony_ci	struct br_port_msg *bpm;
54462306a36Sopenharmony_ci	struct nlmsghdr *nlh;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	nlh = nlmsg_put(skb, pid, seq, type, sizeof(*bpm), 0);
54762306a36Sopenharmony_ci	if (!nlh)
54862306a36Sopenharmony_ci		return -EMSGSIZE;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	bpm = nlmsg_data(nlh);
55162306a36Sopenharmony_ci	memset(bpm, 0, sizeof(*bpm));
55262306a36Sopenharmony_ci	bpm->family = AF_BRIDGE;
55362306a36Sopenharmony_ci	bpm->ifindex = dev->ifindex;
55462306a36Sopenharmony_ci	nest = nla_nest_start_noflag(skb, MDBA_ROUTER);
55562306a36Sopenharmony_ci	if (!nest)
55662306a36Sopenharmony_ci		goto cancel;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	port_nest = nla_nest_start_noflag(skb, MDBA_ROUTER_PORT);
55962306a36Sopenharmony_ci	if (!port_nest)
56062306a36Sopenharmony_ci		goto end;
56162306a36Sopenharmony_ci	if (nla_put_nohdr(skb, sizeof(u32), &ifindex)) {
56262306a36Sopenharmony_ci		nla_nest_cancel(skb, port_nest);
56362306a36Sopenharmony_ci		goto end;
56462306a36Sopenharmony_ci	}
56562306a36Sopenharmony_ci	if (vid && nla_put_u16(skb, MDBA_ROUTER_PATTR_VID, vid)) {
56662306a36Sopenharmony_ci		nla_nest_cancel(skb, port_nest);
56762306a36Sopenharmony_ci		goto end;
56862306a36Sopenharmony_ci	}
56962306a36Sopenharmony_ci	nla_nest_end(skb, port_nest);
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	nla_nest_end(skb, nest);
57262306a36Sopenharmony_ci	nlmsg_end(skb, nlh);
57362306a36Sopenharmony_ci	return 0;
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ciend:
57662306a36Sopenharmony_ci	nla_nest_end(skb, nest);
57762306a36Sopenharmony_cicancel:
57862306a36Sopenharmony_ci	nlmsg_cancel(skb, nlh);
57962306a36Sopenharmony_ci	return -EMSGSIZE;
58062306a36Sopenharmony_ci}
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_cistatic inline size_t rtnl_rtr_nlmsg_size(void)
58362306a36Sopenharmony_ci{
58462306a36Sopenharmony_ci	return NLMSG_ALIGN(sizeof(struct br_port_msg))
58562306a36Sopenharmony_ci		+ nla_total_size(sizeof(__u32))
58662306a36Sopenharmony_ci		+ nla_total_size(sizeof(u16));
58762306a36Sopenharmony_ci}
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_civoid br_rtr_notify(struct net_device *dev, struct net_bridge_mcast_port *pmctx,
59062306a36Sopenharmony_ci		   int type)
59162306a36Sopenharmony_ci{
59262306a36Sopenharmony_ci	struct net *net = dev_net(dev);
59362306a36Sopenharmony_ci	struct sk_buff *skb;
59462306a36Sopenharmony_ci	int err = -ENOBUFS;
59562306a36Sopenharmony_ci	int ifindex;
59662306a36Sopenharmony_ci	u16 vid;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	ifindex = pmctx ? pmctx->port->dev->ifindex : 0;
59962306a36Sopenharmony_ci	vid = pmctx && br_multicast_port_ctx_is_vlan(pmctx) ? pmctx->vlan->vid :
60062306a36Sopenharmony_ci							      0;
60162306a36Sopenharmony_ci	skb = nlmsg_new(rtnl_rtr_nlmsg_size(), GFP_ATOMIC);
60262306a36Sopenharmony_ci	if (!skb)
60362306a36Sopenharmony_ci		goto errout;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	err = nlmsg_populate_rtr_fill(skb, dev, ifindex, vid, 0, 0, type,
60662306a36Sopenharmony_ci				      NTF_SELF);
60762306a36Sopenharmony_ci	if (err < 0) {
60862306a36Sopenharmony_ci		kfree_skb(skb);
60962306a36Sopenharmony_ci		goto errout;
61062306a36Sopenharmony_ci	}
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci	rtnl_notify(skb, net, 0, RTNLGRP_MDB, NULL, GFP_ATOMIC);
61362306a36Sopenharmony_ci	return;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_cierrout:
61662306a36Sopenharmony_ci	rtnl_set_sk_err(net, RTNLGRP_MDB, err);
61762306a36Sopenharmony_ci}
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_cistatic const struct nla_policy
62062306a36Sopenharmony_cibr_mdbe_src_list_entry_pol[MDBE_SRCATTR_MAX + 1] = {
62162306a36Sopenharmony_ci	[MDBE_SRCATTR_ADDRESS] = NLA_POLICY_RANGE(NLA_BINARY,
62262306a36Sopenharmony_ci						  sizeof(struct in_addr),
62362306a36Sopenharmony_ci						  sizeof(struct in6_addr)),
62462306a36Sopenharmony_ci};
62562306a36Sopenharmony_ci
62662306a36Sopenharmony_cistatic const struct nla_policy
62762306a36Sopenharmony_cibr_mdbe_src_list_pol[MDBE_SRC_LIST_MAX + 1] = {
62862306a36Sopenharmony_ci	[MDBE_SRC_LIST_ENTRY] = NLA_POLICY_NESTED(br_mdbe_src_list_entry_pol),
62962306a36Sopenharmony_ci};
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_cistatic const struct nla_policy br_mdbe_attrs_pol[MDBE_ATTR_MAX + 1] = {
63262306a36Sopenharmony_ci	[MDBE_ATTR_SOURCE] = NLA_POLICY_RANGE(NLA_BINARY,
63362306a36Sopenharmony_ci					      sizeof(struct in_addr),
63462306a36Sopenharmony_ci					      sizeof(struct in6_addr)),
63562306a36Sopenharmony_ci	[MDBE_ATTR_GROUP_MODE] = NLA_POLICY_RANGE(NLA_U8, MCAST_EXCLUDE,
63662306a36Sopenharmony_ci						  MCAST_INCLUDE),
63762306a36Sopenharmony_ci	[MDBE_ATTR_SRC_LIST] = NLA_POLICY_NESTED(br_mdbe_src_list_pol),
63862306a36Sopenharmony_ci	[MDBE_ATTR_RTPROT] = NLA_POLICY_MIN(NLA_U8, RTPROT_STATIC),
63962306a36Sopenharmony_ci};
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_cistatic bool is_valid_mdb_source(struct nlattr *attr, __be16 proto,
64262306a36Sopenharmony_ci				struct netlink_ext_ack *extack)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	switch (proto) {
64562306a36Sopenharmony_ci	case htons(ETH_P_IP):
64662306a36Sopenharmony_ci		if (nla_len(attr) != sizeof(struct in_addr)) {
64762306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "IPv4 invalid source address length");
64862306a36Sopenharmony_ci			return false;
64962306a36Sopenharmony_ci		}
65062306a36Sopenharmony_ci		if (ipv4_is_multicast(nla_get_in_addr(attr))) {
65162306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "IPv4 multicast source address is not allowed");
65262306a36Sopenharmony_ci			return false;
65362306a36Sopenharmony_ci		}
65462306a36Sopenharmony_ci		break;
65562306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6)
65662306a36Sopenharmony_ci	case htons(ETH_P_IPV6): {
65762306a36Sopenharmony_ci		struct in6_addr src;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci		if (nla_len(attr) != sizeof(struct in6_addr)) {
66062306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "IPv6 invalid source address length");
66162306a36Sopenharmony_ci			return false;
66262306a36Sopenharmony_ci		}
66362306a36Sopenharmony_ci		src = nla_get_in6_addr(attr);
66462306a36Sopenharmony_ci		if (ipv6_addr_is_multicast(&src)) {
66562306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "IPv6 multicast source address is not allowed");
66662306a36Sopenharmony_ci			return false;
66762306a36Sopenharmony_ci		}
66862306a36Sopenharmony_ci		break;
66962306a36Sopenharmony_ci	}
67062306a36Sopenharmony_ci#endif
67162306a36Sopenharmony_ci	default:
67262306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Invalid protocol used with source address");
67362306a36Sopenharmony_ci		return false;
67462306a36Sopenharmony_ci	}
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_ci	return true;
67762306a36Sopenharmony_ci}
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_cistatic struct net_bridge_mcast *
68062306a36Sopenharmony_ci__br_mdb_choose_context(struct net_bridge *br,
68162306a36Sopenharmony_ci			const struct br_mdb_entry *entry,
68262306a36Sopenharmony_ci			struct netlink_ext_ack *extack)
68362306a36Sopenharmony_ci{
68462306a36Sopenharmony_ci	struct net_bridge_mcast *brmctx = NULL;
68562306a36Sopenharmony_ci	struct net_bridge_vlan *v;
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	if (!br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
68862306a36Sopenharmony_ci		brmctx = &br->multicast_ctx;
68962306a36Sopenharmony_ci		goto out;
69062306a36Sopenharmony_ci	}
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci	if (!entry->vid) {
69362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Cannot add an entry without a vlan when vlan snooping is enabled");
69462306a36Sopenharmony_ci		goto out;
69562306a36Sopenharmony_ci	}
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	v = br_vlan_find(br_vlan_group(br), entry->vid);
69862306a36Sopenharmony_ci	if (!v) {
69962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Vlan is not configured");
70062306a36Sopenharmony_ci		goto out;
70162306a36Sopenharmony_ci	}
70262306a36Sopenharmony_ci	if (br_multicast_ctx_vlan_global_disabled(&v->br_mcast_ctx)) {
70362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Vlan's multicast processing is disabled");
70462306a36Sopenharmony_ci		goto out;
70562306a36Sopenharmony_ci	}
70662306a36Sopenharmony_ci	brmctx = &v->br_mcast_ctx;
70762306a36Sopenharmony_ciout:
70862306a36Sopenharmony_ci	return brmctx;
70962306a36Sopenharmony_ci}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_cistatic int br_mdb_replace_group_sg(const struct br_mdb_config *cfg,
71262306a36Sopenharmony_ci				   struct net_bridge_mdb_entry *mp,
71362306a36Sopenharmony_ci				   struct net_bridge_port_group *pg,
71462306a36Sopenharmony_ci				   struct net_bridge_mcast *brmctx,
71562306a36Sopenharmony_ci				   unsigned char flags)
71662306a36Sopenharmony_ci{
71762306a36Sopenharmony_ci	unsigned long now = jiffies;
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci	pg->flags = flags;
72062306a36Sopenharmony_ci	pg->rt_protocol = cfg->rt_protocol;
72162306a36Sopenharmony_ci	if (!(flags & MDB_PG_FLAGS_PERMANENT) && !cfg->src_entry)
72262306a36Sopenharmony_ci		mod_timer(&pg->timer,
72362306a36Sopenharmony_ci			  now + brmctx->multicast_membership_interval);
72462306a36Sopenharmony_ci	else
72562306a36Sopenharmony_ci		del_timer(&pg->timer);
72662306a36Sopenharmony_ci
72762306a36Sopenharmony_ci	br_mdb_notify(cfg->br->dev, mp, pg, RTM_NEWMDB);
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ci	return 0;
73062306a36Sopenharmony_ci}
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_cistatic int br_mdb_add_group_sg(const struct br_mdb_config *cfg,
73362306a36Sopenharmony_ci			       struct net_bridge_mdb_entry *mp,
73462306a36Sopenharmony_ci			       struct net_bridge_mcast *brmctx,
73562306a36Sopenharmony_ci			       unsigned char flags,
73662306a36Sopenharmony_ci			       struct netlink_ext_ack *extack)
73762306a36Sopenharmony_ci{
73862306a36Sopenharmony_ci	struct net_bridge_port_group __rcu **pp;
73962306a36Sopenharmony_ci	struct net_bridge_port_group *p;
74062306a36Sopenharmony_ci	unsigned long now = jiffies;
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci	for (pp = &mp->ports;
74362306a36Sopenharmony_ci	     (p = mlock_dereference(*pp, cfg->br)) != NULL;
74462306a36Sopenharmony_ci	     pp = &p->next) {
74562306a36Sopenharmony_ci		if (p->key.port == cfg->p) {
74662306a36Sopenharmony_ci			if (!(cfg->nlflags & NLM_F_REPLACE)) {
74762306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack, "(S, G) group is already joined by port");
74862306a36Sopenharmony_ci				return -EEXIST;
74962306a36Sopenharmony_ci			}
75062306a36Sopenharmony_ci			return br_mdb_replace_group_sg(cfg, mp, p, brmctx,
75162306a36Sopenharmony_ci						       flags);
75262306a36Sopenharmony_ci		}
75362306a36Sopenharmony_ci		if ((unsigned long)p->key.port < (unsigned long)cfg->p)
75462306a36Sopenharmony_ci			break;
75562306a36Sopenharmony_ci	}
75662306a36Sopenharmony_ci
75762306a36Sopenharmony_ci	p = br_multicast_new_port_group(cfg->p, &cfg->group, *pp, flags, NULL,
75862306a36Sopenharmony_ci					MCAST_INCLUDE, cfg->rt_protocol, extack);
75962306a36Sopenharmony_ci	if (unlikely(!p))
76062306a36Sopenharmony_ci		return -ENOMEM;
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	rcu_assign_pointer(*pp, p);
76362306a36Sopenharmony_ci	if (!(flags & MDB_PG_FLAGS_PERMANENT) && !cfg->src_entry)
76462306a36Sopenharmony_ci		mod_timer(&p->timer,
76562306a36Sopenharmony_ci			  now + brmctx->multicast_membership_interval);
76662306a36Sopenharmony_ci	br_mdb_notify(cfg->br->dev, mp, p, RTM_NEWMDB);
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci	/* All of (*, G) EXCLUDE ports need to be added to the new (S, G) for
76962306a36Sopenharmony_ci	 * proper replication.
77062306a36Sopenharmony_ci	 */
77162306a36Sopenharmony_ci	if (br_multicast_should_handle_mode(brmctx, cfg->group.proto)) {
77262306a36Sopenharmony_ci		struct net_bridge_mdb_entry *star_mp;
77362306a36Sopenharmony_ci		struct br_ip star_group;
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci		star_group = p->key.addr;
77662306a36Sopenharmony_ci		memset(&star_group.src, 0, sizeof(star_group.src));
77762306a36Sopenharmony_ci		star_mp = br_mdb_ip_get(cfg->br, &star_group);
77862306a36Sopenharmony_ci		if (star_mp)
77962306a36Sopenharmony_ci			br_multicast_sg_add_exclude_ports(star_mp, p);
78062306a36Sopenharmony_ci	}
78162306a36Sopenharmony_ci
78262306a36Sopenharmony_ci	return 0;
78362306a36Sopenharmony_ci}
78462306a36Sopenharmony_ci
78562306a36Sopenharmony_cistatic int br_mdb_add_group_src_fwd(const struct br_mdb_config *cfg,
78662306a36Sopenharmony_ci				    struct br_ip *src_ip,
78762306a36Sopenharmony_ci				    struct net_bridge_mcast *brmctx,
78862306a36Sopenharmony_ci				    struct netlink_ext_ack *extack)
78962306a36Sopenharmony_ci{
79062306a36Sopenharmony_ci	struct net_bridge_mdb_entry *sgmp;
79162306a36Sopenharmony_ci	struct br_mdb_config sg_cfg;
79262306a36Sopenharmony_ci	struct br_ip sg_ip;
79362306a36Sopenharmony_ci	u8 flags = 0;
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	sg_ip = cfg->group;
79662306a36Sopenharmony_ci	sg_ip.src = src_ip->src;
79762306a36Sopenharmony_ci	sgmp = br_multicast_new_group(cfg->br, &sg_ip);
79862306a36Sopenharmony_ci	if (IS_ERR(sgmp)) {
79962306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Failed to add (S, G) MDB entry");
80062306a36Sopenharmony_ci		return PTR_ERR(sgmp);
80162306a36Sopenharmony_ci	}
80262306a36Sopenharmony_ci
80362306a36Sopenharmony_ci	if (cfg->entry->state == MDB_PERMANENT)
80462306a36Sopenharmony_ci		flags |= MDB_PG_FLAGS_PERMANENT;
80562306a36Sopenharmony_ci	if (cfg->filter_mode == MCAST_EXCLUDE)
80662306a36Sopenharmony_ci		flags |= MDB_PG_FLAGS_BLOCKED;
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	memset(&sg_cfg, 0, sizeof(sg_cfg));
80962306a36Sopenharmony_ci	sg_cfg.br = cfg->br;
81062306a36Sopenharmony_ci	sg_cfg.p = cfg->p;
81162306a36Sopenharmony_ci	sg_cfg.entry = cfg->entry;
81262306a36Sopenharmony_ci	sg_cfg.group = sg_ip;
81362306a36Sopenharmony_ci	sg_cfg.src_entry = true;
81462306a36Sopenharmony_ci	sg_cfg.filter_mode = MCAST_INCLUDE;
81562306a36Sopenharmony_ci	sg_cfg.rt_protocol = cfg->rt_protocol;
81662306a36Sopenharmony_ci	sg_cfg.nlflags = cfg->nlflags;
81762306a36Sopenharmony_ci	return br_mdb_add_group_sg(&sg_cfg, sgmp, brmctx, flags, extack);
81862306a36Sopenharmony_ci}
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_cistatic int br_mdb_add_group_src(const struct br_mdb_config *cfg,
82162306a36Sopenharmony_ci				struct net_bridge_port_group *pg,
82262306a36Sopenharmony_ci				struct net_bridge_mcast *brmctx,
82362306a36Sopenharmony_ci				struct br_mdb_src_entry *src,
82462306a36Sopenharmony_ci				struct netlink_ext_ack *extack)
82562306a36Sopenharmony_ci{
82662306a36Sopenharmony_ci	struct net_bridge_group_src *ent;
82762306a36Sopenharmony_ci	unsigned long now = jiffies;
82862306a36Sopenharmony_ci	int err;
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	ent = br_multicast_find_group_src(pg, &src->addr);
83162306a36Sopenharmony_ci	if (!ent) {
83262306a36Sopenharmony_ci		ent = br_multicast_new_group_src(pg, &src->addr);
83362306a36Sopenharmony_ci		if (!ent) {
83462306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Failed to add new source entry");
83562306a36Sopenharmony_ci			return -ENOSPC;
83662306a36Sopenharmony_ci		}
83762306a36Sopenharmony_ci	} else if (!(cfg->nlflags & NLM_F_REPLACE)) {
83862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Source entry already exists");
83962306a36Sopenharmony_ci		return -EEXIST;
84062306a36Sopenharmony_ci	}
84162306a36Sopenharmony_ci
84262306a36Sopenharmony_ci	if (cfg->filter_mode == MCAST_INCLUDE &&
84362306a36Sopenharmony_ci	    cfg->entry->state == MDB_TEMPORARY)
84462306a36Sopenharmony_ci		mod_timer(&ent->timer, now + br_multicast_gmi(brmctx));
84562306a36Sopenharmony_ci	else
84662306a36Sopenharmony_ci		del_timer(&ent->timer);
84762306a36Sopenharmony_ci
84862306a36Sopenharmony_ci	/* Install a (S, G) forwarding entry for the source. */
84962306a36Sopenharmony_ci	err = br_mdb_add_group_src_fwd(cfg, &src->addr, brmctx, extack);
85062306a36Sopenharmony_ci	if (err)
85162306a36Sopenharmony_ci		goto err_del_sg;
85262306a36Sopenharmony_ci
85362306a36Sopenharmony_ci	ent->flags = BR_SGRP_F_INSTALLED | BR_SGRP_F_USER_ADDED;
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	return 0;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_cierr_del_sg:
85862306a36Sopenharmony_ci	__br_multicast_del_group_src(ent);
85962306a36Sopenharmony_ci	return err;
86062306a36Sopenharmony_ci}
86162306a36Sopenharmony_ci
86262306a36Sopenharmony_cistatic void br_mdb_del_group_src(struct net_bridge_port_group *pg,
86362306a36Sopenharmony_ci				 struct br_mdb_src_entry *src)
86462306a36Sopenharmony_ci{
86562306a36Sopenharmony_ci	struct net_bridge_group_src *ent;
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_ci	ent = br_multicast_find_group_src(pg, &src->addr);
86862306a36Sopenharmony_ci	if (WARN_ON_ONCE(!ent))
86962306a36Sopenharmony_ci		return;
87062306a36Sopenharmony_ci	br_multicast_del_group_src(ent, false);
87162306a36Sopenharmony_ci}
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_cistatic int br_mdb_add_group_srcs(const struct br_mdb_config *cfg,
87462306a36Sopenharmony_ci				 struct net_bridge_port_group *pg,
87562306a36Sopenharmony_ci				 struct net_bridge_mcast *brmctx,
87662306a36Sopenharmony_ci				 struct netlink_ext_ack *extack)
87762306a36Sopenharmony_ci{
87862306a36Sopenharmony_ci	int i, err;
87962306a36Sopenharmony_ci
88062306a36Sopenharmony_ci	for (i = 0; i < cfg->num_src_entries; i++) {
88162306a36Sopenharmony_ci		err = br_mdb_add_group_src(cfg, pg, brmctx,
88262306a36Sopenharmony_ci					   &cfg->src_entries[i], extack);
88362306a36Sopenharmony_ci		if (err)
88462306a36Sopenharmony_ci			goto err_del_group_srcs;
88562306a36Sopenharmony_ci	}
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	return 0;
88862306a36Sopenharmony_ci
88962306a36Sopenharmony_cierr_del_group_srcs:
89062306a36Sopenharmony_ci	for (i--; i >= 0; i--)
89162306a36Sopenharmony_ci		br_mdb_del_group_src(pg, &cfg->src_entries[i]);
89262306a36Sopenharmony_ci	return err;
89362306a36Sopenharmony_ci}
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_cistatic int br_mdb_replace_group_srcs(const struct br_mdb_config *cfg,
89662306a36Sopenharmony_ci				     struct net_bridge_port_group *pg,
89762306a36Sopenharmony_ci				     struct net_bridge_mcast *brmctx,
89862306a36Sopenharmony_ci				     struct netlink_ext_ack *extack)
89962306a36Sopenharmony_ci{
90062306a36Sopenharmony_ci	struct net_bridge_group_src *ent;
90162306a36Sopenharmony_ci	struct hlist_node *tmp;
90262306a36Sopenharmony_ci	int err;
90362306a36Sopenharmony_ci
90462306a36Sopenharmony_ci	hlist_for_each_entry(ent, &pg->src_list, node)
90562306a36Sopenharmony_ci		ent->flags |= BR_SGRP_F_DELETE;
90662306a36Sopenharmony_ci
90762306a36Sopenharmony_ci	err = br_mdb_add_group_srcs(cfg, pg, brmctx, extack);
90862306a36Sopenharmony_ci	if (err)
90962306a36Sopenharmony_ci		goto err_clear_delete;
91062306a36Sopenharmony_ci
91162306a36Sopenharmony_ci	hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node) {
91262306a36Sopenharmony_ci		if (ent->flags & BR_SGRP_F_DELETE)
91362306a36Sopenharmony_ci			br_multicast_del_group_src(ent, false);
91462306a36Sopenharmony_ci	}
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	return 0;
91762306a36Sopenharmony_ci
91862306a36Sopenharmony_cierr_clear_delete:
91962306a36Sopenharmony_ci	hlist_for_each_entry(ent, &pg->src_list, node)
92062306a36Sopenharmony_ci		ent->flags &= ~BR_SGRP_F_DELETE;
92162306a36Sopenharmony_ci	return err;
92262306a36Sopenharmony_ci}
92362306a36Sopenharmony_ci
92462306a36Sopenharmony_cistatic int br_mdb_replace_group_star_g(const struct br_mdb_config *cfg,
92562306a36Sopenharmony_ci				       struct net_bridge_mdb_entry *mp,
92662306a36Sopenharmony_ci				       struct net_bridge_port_group *pg,
92762306a36Sopenharmony_ci				       struct net_bridge_mcast *brmctx,
92862306a36Sopenharmony_ci				       unsigned char flags,
92962306a36Sopenharmony_ci				       struct netlink_ext_ack *extack)
93062306a36Sopenharmony_ci{
93162306a36Sopenharmony_ci	unsigned long now = jiffies;
93262306a36Sopenharmony_ci	int err;
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	err = br_mdb_replace_group_srcs(cfg, pg, brmctx, extack);
93562306a36Sopenharmony_ci	if (err)
93662306a36Sopenharmony_ci		return err;
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_ci	pg->flags = flags;
93962306a36Sopenharmony_ci	pg->filter_mode = cfg->filter_mode;
94062306a36Sopenharmony_ci	pg->rt_protocol = cfg->rt_protocol;
94162306a36Sopenharmony_ci	if (!(flags & MDB_PG_FLAGS_PERMANENT) &&
94262306a36Sopenharmony_ci	    cfg->filter_mode == MCAST_EXCLUDE)
94362306a36Sopenharmony_ci		mod_timer(&pg->timer,
94462306a36Sopenharmony_ci			  now + brmctx->multicast_membership_interval);
94562306a36Sopenharmony_ci	else
94662306a36Sopenharmony_ci		del_timer(&pg->timer);
94762306a36Sopenharmony_ci
94862306a36Sopenharmony_ci	br_mdb_notify(cfg->br->dev, mp, pg, RTM_NEWMDB);
94962306a36Sopenharmony_ci
95062306a36Sopenharmony_ci	if (br_multicast_should_handle_mode(brmctx, cfg->group.proto))
95162306a36Sopenharmony_ci		br_multicast_star_g_handle_mode(pg, cfg->filter_mode);
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	return 0;
95462306a36Sopenharmony_ci}
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_cistatic int br_mdb_add_group_star_g(const struct br_mdb_config *cfg,
95762306a36Sopenharmony_ci				   struct net_bridge_mdb_entry *mp,
95862306a36Sopenharmony_ci				   struct net_bridge_mcast *brmctx,
95962306a36Sopenharmony_ci				   unsigned char flags,
96062306a36Sopenharmony_ci				   struct netlink_ext_ack *extack)
96162306a36Sopenharmony_ci{
96262306a36Sopenharmony_ci	struct net_bridge_port_group __rcu **pp;
96362306a36Sopenharmony_ci	struct net_bridge_port_group *p;
96462306a36Sopenharmony_ci	unsigned long now = jiffies;
96562306a36Sopenharmony_ci	int err;
96662306a36Sopenharmony_ci
96762306a36Sopenharmony_ci	for (pp = &mp->ports;
96862306a36Sopenharmony_ci	     (p = mlock_dereference(*pp, cfg->br)) != NULL;
96962306a36Sopenharmony_ci	     pp = &p->next) {
97062306a36Sopenharmony_ci		if (p->key.port == cfg->p) {
97162306a36Sopenharmony_ci			if (!(cfg->nlflags & NLM_F_REPLACE)) {
97262306a36Sopenharmony_ci				NL_SET_ERR_MSG_MOD(extack, "(*, G) group is already joined by port");
97362306a36Sopenharmony_ci				return -EEXIST;
97462306a36Sopenharmony_ci			}
97562306a36Sopenharmony_ci			return br_mdb_replace_group_star_g(cfg, mp, p, brmctx,
97662306a36Sopenharmony_ci							   flags, extack);
97762306a36Sopenharmony_ci		}
97862306a36Sopenharmony_ci		if ((unsigned long)p->key.port < (unsigned long)cfg->p)
97962306a36Sopenharmony_ci			break;
98062306a36Sopenharmony_ci	}
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	p = br_multicast_new_port_group(cfg->p, &cfg->group, *pp, flags, NULL,
98362306a36Sopenharmony_ci					cfg->filter_mode, cfg->rt_protocol,
98462306a36Sopenharmony_ci					extack);
98562306a36Sopenharmony_ci	if (unlikely(!p))
98662306a36Sopenharmony_ci		return -ENOMEM;
98762306a36Sopenharmony_ci
98862306a36Sopenharmony_ci	err = br_mdb_add_group_srcs(cfg, p, brmctx, extack);
98962306a36Sopenharmony_ci	if (err)
99062306a36Sopenharmony_ci		goto err_del_port_group;
99162306a36Sopenharmony_ci
99262306a36Sopenharmony_ci	rcu_assign_pointer(*pp, p);
99362306a36Sopenharmony_ci	if (!(flags & MDB_PG_FLAGS_PERMANENT) &&
99462306a36Sopenharmony_ci	    cfg->filter_mode == MCAST_EXCLUDE)
99562306a36Sopenharmony_ci		mod_timer(&p->timer,
99662306a36Sopenharmony_ci			  now + brmctx->multicast_membership_interval);
99762306a36Sopenharmony_ci	br_mdb_notify(cfg->br->dev, mp, p, RTM_NEWMDB);
99862306a36Sopenharmony_ci	/* If we are adding a new EXCLUDE port group (*, G), it needs to be
99962306a36Sopenharmony_ci	 * also added to all (S, G) entries for proper replication.
100062306a36Sopenharmony_ci	 */
100162306a36Sopenharmony_ci	if (br_multicast_should_handle_mode(brmctx, cfg->group.proto) &&
100262306a36Sopenharmony_ci	    cfg->filter_mode == MCAST_EXCLUDE)
100362306a36Sopenharmony_ci		br_multicast_star_g_handle_mode(p, MCAST_EXCLUDE);
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci	return 0;
100662306a36Sopenharmony_ci
100762306a36Sopenharmony_cierr_del_port_group:
100862306a36Sopenharmony_ci	br_multicast_del_port_group(p);
100962306a36Sopenharmony_ci	return err;
101062306a36Sopenharmony_ci}
101162306a36Sopenharmony_ci
101262306a36Sopenharmony_cistatic int br_mdb_add_group(const struct br_mdb_config *cfg,
101362306a36Sopenharmony_ci			    struct netlink_ext_ack *extack)
101462306a36Sopenharmony_ci{
101562306a36Sopenharmony_ci	struct br_mdb_entry *entry = cfg->entry;
101662306a36Sopenharmony_ci	struct net_bridge_port *port = cfg->p;
101762306a36Sopenharmony_ci	struct net_bridge_mdb_entry *mp;
101862306a36Sopenharmony_ci	struct net_bridge *br = cfg->br;
101962306a36Sopenharmony_ci	struct net_bridge_mcast *brmctx;
102062306a36Sopenharmony_ci	struct br_ip group = cfg->group;
102162306a36Sopenharmony_ci	unsigned char flags = 0;
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_ci	brmctx = __br_mdb_choose_context(br, entry, extack);
102462306a36Sopenharmony_ci	if (!brmctx)
102562306a36Sopenharmony_ci		return -EINVAL;
102662306a36Sopenharmony_ci
102762306a36Sopenharmony_ci	mp = br_multicast_new_group(br, &group);
102862306a36Sopenharmony_ci	if (IS_ERR(mp))
102962306a36Sopenharmony_ci		return PTR_ERR(mp);
103062306a36Sopenharmony_ci
103162306a36Sopenharmony_ci	/* host join */
103262306a36Sopenharmony_ci	if (!port) {
103362306a36Sopenharmony_ci		if (mp->host_joined) {
103462306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Group is already joined by host");
103562306a36Sopenharmony_ci			return -EEXIST;
103662306a36Sopenharmony_ci		}
103762306a36Sopenharmony_ci
103862306a36Sopenharmony_ci		br_multicast_host_join(brmctx, mp, false);
103962306a36Sopenharmony_ci		br_mdb_notify(br->dev, mp, NULL, RTM_NEWMDB);
104062306a36Sopenharmony_ci
104162306a36Sopenharmony_ci		return 0;
104262306a36Sopenharmony_ci	}
104362306a36Sopenharmony_ci
104462306a36Sopenharmony_ci	if (entry->state == MDB_PERMANENT)
104562306a36Sopenharmony_ci		flags |= MDB_PG_FLAGS_PERMANENT;
104662306a36Sopenharmony_ci
104762306a36Sopenharmony_ci	if (br_multicast_is_star_g(&group))
104862306a36Sopenharmony_ci		return br_mdb_add_group_star_g(cfg, mp, brmctx, flags, extack);
104962306a36Sopenharmony_ci	else
105062306a36Sopenharmony_ci		return br_mdb_add_group_sg(cfg, mp, brmctx, flags, extack);
105162306a36Sopenharmony_ci}
105262306a36Sopenharmony_ci
105362306a36Sopenharmony_cistatic int __br_mdb_add(const struct br_mdb_config *cfg,
105462306a36Sopenharmony_ci			struct netlink_ext_ack *extack)
105562306a36Sopenharmony_ci{
105662306a36Sopenharmony_ci	int ret;
105762306a36Sopenharmony_ci
105862306a36Sopenharmony_ci	spin_lock_bh(&cfg->br->multicast_lock);
105962306a36Sopenharmony_ci	ret = br_mdb_add_group(cfg, extack);
106062306a36Sopenharmony_ci	spin_unlock_bh(&cfg->br->multicast_lock);
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci	return ret;
106362306a36Sopenharmony_ci}
106462306a36Sopenharmony_ci
106562306a36Sopenharmony_cistatic int br_mdb_config_src_entry_init(struct nlattr *src_entry,
106662306a36Sopenharmony_ci					struct br_mdb_src_entry *src,
106762306a36Sopenharmony_ci					__be16 proto,
106862306a36Sopenharmony_ci					struct netlink_ext_ack *extack)
106962306a36Sopenharmony_ci{
107062306a36Sopenharmony_ci	struct nlattr *tb[MDBE_SRCATTR_MAX + 1];
107162306a36Sopenharmony_ci	int err;
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	err = nla_parse_nested(tb, MDBE_SRCATTR_MAX, src_entry,
107462306a36Sopenharmony_ci			       br_mdbe_src_list_entry_pol, extack);
107562306a36Sopenharmony_ci	if (err)
107662306a36Sopenharmony_ci		return err;
107762306a36Sopenharmony_ci
107862306a36Sopenharmony_ci	if (NL_REQ_ATTR_CHECK(extack, src_entry, tb, MDBE_SRCATTR_ADDRESS))
107962306a36Sopenharmony_ci		return -EINVAL;
108062306a36Sopenharmony_ci
108162306a36Sopenharmony_ci	if (!is_valid_mdb_source(tb[MDBE_SRCATTR_ADDRESS], proto, extack))
108262306a36Sopenharmony_ci		return -EINVAL;
108362306a36Sopenharmony_ci
108462306a36Sopenharmony_ci	src->addr.proto = proto;
108562306a36Sopenharmony_ci	nla_memcpy(&src->addr.src, tb[MDBE_SRCATTR_ADDRESS],
108662306a36Sopenharmony_ci		   nla_len(tb[MDBE_SRCATTR_ADDRESS]));
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_ci	return 0;
108962306a36Sopenharmony_ci}
109062306a36Sopenharmony_ci
109162306a36Sopenharmony_cistatic int br_mdb_config_src_list_init(struct nlattr *src_list,
109262306a36Sopenharmony_ci				       struct br_mdb_config *cfg,
109362306a36Sopenharmony_ci				       struct netlink_ext_ack *extack)
109462306a36Sopenharmony_ci{
109562306a36Sopenharmony_ci	struct nlattr *src_entry;
109662306a36Sopenharmony_ci	int rem, err;
109762306a36Sopenharmony_ci	int i = 0;
109862306a36Sopenharmony_ci
109962306a36Sopenharmony_ci	nla_for_each_nested(src_entry, src_list, rem)
110062306a36Sopenharmony_ci		cfg->num_src_entries++;
110162306a36Sopenharmony_ci
110262306a36Sopenharmony_ci	if (cfg->num_src_entries >= PG_SRC_ENT_LIMIT) {
110362306a36Sopenharmony_ci		NL_SET_ERR_MSG_FMT_MOD(extack, "Exceeded maximum number of source entries (%u)",
110462306a36Sopenharmony_ci				       PG_SRC_ENT_LIMIT - 1);
110562306a36Sopenharmony_ci		return -EINVAL;
110662306a36Sopenharmony_ci	}
110762306a36Sopenharmony_ci
110862306a36Sopenharmony_ci	cfg->src_entries = kcalloc(cfg->num_src_entries,
110962306a36Sopenharmony_ci				   sizeof(struct br_mdb_src_entry), GFP_KERNEL);
111062306a36Sopenharmony_ci	if (!cfg->src_entries)
111162306a36Sopenharmony_ci		return -ENOMEM;
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci	nla_for_each_nested(src_entry, src_list, rem) {
111462306a36Sopenharmony_ci		err = br_mdb_config_src_entry_init(src_entry,
111562306a36Sopenharmony_ci						   &cfg->src_entries[i],
111662306a36Sopenharmony_ci						   cfg->entry->addr.proto,
111762306a36Sopenharmony_ci						   extack);
111862306a36Sopenharmony_ci		if (err)
111962306a36Sopenharmony_ci			goto err_src_entry_init;
112062306a36Sopenharmony_ci		i++;
112162306a36Sopenharmony_ci	}
112262306a36Sopenharmony_ci
112362306a36Sopenharmony_ci	return 0;
112462306a36Sopenharmony_ci
112562306a36Sopenharmony_cierr_src_entry_init:
112662306a36Sopenharmony_ci	kfree(cfg->src_entries);
112762306a36Sopenharmony_ci	return err;
112862306a36Sopenharmony_ci}
112962306a36Sopenharmony_ci
113062306a36Sopenharmony_cistatic void br_mdb_config_src_list_fini(struct br_mdb_config *cfg)
113162306a36Sopenharmony_ci{
113262306a36Sopenharmony_ci	kfree(cfg->src_entries);
113362306a36Sopenharmony_ci}
113462306a36Sopenharmony_ci
113562306a36Sopenharmony_cistatic int br_mdb_config_attrs_init(struct nlattr *set_attrs,
113662306a36Sopenharmony_ci				    struct br_mdb_config *cfg,
113762306a36Sopenharmony_ci				    struct netlink_ext_ack *extack)
113862306a36Sopenharmony_ci{
113962306a36Sopenharmony_ci	struct nlattr *mdb_attrs[MDBE_ATTR_MAX + 1];
114062306a36Sopenharmony_ci	int err;
114162306a36Sopenharmony_ci
114262306a36Sopenharmony_ci	err = nla_parse_nested(mdb_attrs, MDBE_ATTR_MAX, set_attrs,
114362306a36Sopenharmony_ci			       br_mdbe_attrs_pol, extack);
114462306a36Sopenharmony_ci	if (err)
114562306a36Sopenharmony_ci		return err;
114662306a36Sopenharmony_ci
114762306a36Sopenharmony_ci	if (mdb_attrs[MDBE_ATTR_SOURCE] &&
114862306a36Sopenharmony_ci	    !is_valid_mdb_source(mdb_attrs[MDBE_ATTR_SOURCE],
114962306a36Sopenharmony_ci				 cfg->entry->addr.proto, extack))
115062306a36Sopenharmony_ci		return -EINVAL;
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci	__mdb_entry_to_br_ip(cfg->entry, &cfg->group, mdb_attrs);
115362306a36Sopenharmony_ci
115462306a36Sopenharmony_ci	if (mdb_attrs[MDBE_ATTR_GROUP_MODE]) {
115562306a36Sopenharmony_ci		if (!cfg->p) {
115662306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Filter mode cannot be set for host groups");
115762306a36Sopenharmony_ci			return -EINVAL;
115862306a36Sopenharmony_ci		}
115962306a36Sopenharmony_ci		if (!br_multicast_is_star_g(&cfg->group)) {
116062306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Filter mode can only be set for (*, G) entries");
116162306a36Sopenharmony_ci			return -EINVAL;
116262306a36Sopenharmony_ci		}
116362306a36Sopenharmony_ci		cfg->filter_mode = nla_get_u8(mdb_attrs[MDBE_ATTR_GROUP_MODE]);
116462306a36Sopenharmony_ci	} else {
116562306a36Sopenharmony_ci		cfg->filter_mode = MCAST_EXCLUDE;
116662306a36Sopenharmony_ci	}
116762306a36Sopenharmony_ci
116862306a36Sopenharmony_ci	if (mdb_attrs[MDBE_ATTR_SRC_LIST]) {
116962306a36Sopenharmony_ci		if (!cfg->p) {
117062306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Source list cannot be set for host groups");
117162306a36Sopenharmony_ci			return -EINVAL;
117262306a36Sopenharmony_ci		}
117362306a36Sopenharmony_ci		if (!br_multicast_is_star_g(&cfg->group)) {
117462306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Source list can only be set for (*, G) entries");
117562306a36Sopenharmony_ci			return -EINVAL;
117662306a36Sopenharmony_ci		}
117762306a36Sopenharmony_ci		if (!mdb_attrs[MDBE_ATTR_GROUP_MODE]) {
117862306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Source list cannot be set without filter mode");
117962306a36Sopenharmony_ci			return -EINVAL;
118062306a36Sopenharmony_ci		}
118162306a36Sopenharmony_ci		err = br_mdb_config_src_list_init(mdb_attrs[MDBE_ATTR_SRC_LIST],
118262306a36Sopenharmony_ci						  cfg, extack);
118362306a36Sopenharmony_ci		if (err)
118462306a36Sopenharmony_ci			return err;
118562306a36Sopenharmony_ci	}
118662306a36Sopenharmony_ci
118762306a36Sopenharmony_ci	if (!cfg->num_src_entries && cfg->filter_mode == MCAST_INCLUDE) {
118862306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Cannot add (*, G) INCLUDE with an empty source list");
118962306a36Sopenharmony_ci		return -EINVAL;
119062306a36Sopenharmony_ci	}
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_ci	if (mdb_attrs[MDBE_ATTR_RTPROT]) {
119362306a36Sopenharmony_ci		if (!cfg->p) {
119462306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Protocol cannot be set for host groups");
119562306a36Sopenharmony_ci			return -EINVAL;
119662306a36Sopenharmony_ci		}
119762306a36Sopenharmony_ci		cfg->rt_protocol = nla_get_u8(mdb_attrs[MDBE_ATTR_RTPROT]);
119862306a36Sopenharmony_ci	}
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci	return 0;
120162306a36Sopenharmony_ci}
120262306a36Sopenharmony_ci
120362306a36Sopenharmony_cistatic int br_mdb_config_init(struct br_mdb_config *cfg, struct net_device *dev,
120462306a36Sopenharmony_ci			      struct nlattr *tb[], u16 nlmsg_flags,
120562306a36Sopenharmony_ci			      struct netlink_ext_ack *extack)
120662306a36Sopenharmony_ci{
120762306a36Sopenharmony_ci	struct net *net = dev_net(dev);
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	memset(cfg, 0, sizeof(*cfg));
121062306a36Sopenharmony_ci	cfg->filter_mode = MCAST_EXCLUDE;
121162306a36Sopenharmony_ci	cfg->rt_protocol = RTPROT_STATIC;
121262306a36Sopenharmony_ci	cfg->nlflags = nlmsg_flags;
121362306a36Sopenharmony_ci
121462306a36Sopenharmony_ci	cfg->br = netdev_priv(dev);
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_ci	if (!netif_running(cfg->br->dev)) {
121762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Bridge device is not running");
121862306a36Sopenharmony_ci		return -EINVAL;
121962306a36Sopenharmony_ci	}
122062306a36Sopenharmony_ci
122162306a36Sopenharmony_ci	if (!br_opt_get(cfg->br, BROPT_MULTICAST_ENABLED)) {
122262306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Bridge's multicast processing is disabled");
122362306a36Sopenharmony_ci		return -EINVAL;
122462306a36Sopenharmony_ci	}
122562306a36Sopenharmony_ci
122662306a36Sopenharmony_ci	cfg->entry = nla_data(tb[MDBA_SET_ENTRY]);
122762306a36Sopenharmony_ci
122862306a36Sopenharmony_ci	if (cfg->entry->ifindex != cfg->br->dev->ifindex) {
122962306a36Sopenharmony_ci		struct net_device *pdev;
123062306a36Sopenharmony_ci
123162306a36Sopenharmony_ci		pdev = __dev_get_by_index(net, cfg->entry->ifindex);
123262306a36Sopenharmony_ci		if (!pdev) {
123362306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Port net device doesn't exist");
123462306a36Sopenharmony_ci			return -ENODEV;
123562306a36Sopenharmony_ci		}
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_ci		cfg->p = br_port_get_rtnl(pdev);
123862306a36Sopenharmony_ci		if (!cfg->p) {
123962306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Net device is not a bridge port");
124062306a36Sopenharmony_ci			return -EINVAL;
124162306a36Sopenharmony_ci		}
124262306a36Sopenharmony_ci
124362306a36Sopenharmony_ci		if (cfg->p->br != cfg->br) {
124462306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Port belongs to a different bridge device");
124562306a36Sopenharmony_ci			return -EINVAL;
124662306a36Sopenharmony_ci		}
124762306a36Sopenharmony_ci	}
124862306a36Sopenharmony_ci
124962306a36Sopenharmony_ci	if (cfg->entry->addr.proto == htons(ETH_P_IP) &&
125062306a36Sopenharmony_ci	    ipv4_is_zeronet(cfg->entry->addr.u.ip4)) {
125162306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "IPv4 entry group address 0.0.0.0 is not allowed");
125262306a36Sopenharmony_ci		return -EINVAL;
125362306a36Sopenharmony_ci	}
125462306a36Sopenharmony_ci
125562306a36Sopenharmony_ci	if (tb[MDBA_SET_ENTRY_ATTRS])
125662306a36Sopenharmony_ci		return br_mdb_config_attrs_init(tb[MDBA_SET_ENTRY_ATTRS], cfg,
125762306a36Sopenharmony_ci						extack);
125862306a36Sopenharmony_ci	else
125962306a36Sopenharmony_ci		__mdb_entry_to_br_ip(cfg->entry, &cfg->group, NULL);
126062306a36Sopenharmony_ci
126162306a36Sopenharmony_ci	return 0;
126262306a36Sopenharmony_ci}
126362306a36Sopenharmony_ci
126462306a36Sopenharmony_cistatic void br_mdb_config_fini(struct br_mdb_config *cfg)
126562306a36Sopenharmony_ci{
126662306a36Sopenharmony_ci	br_mdb_config_src_list_fini(cfg);
126762306a36Sopenharmony_ci}
126862306a36Sopenharmony_ci
126962306a36Sopenharmony_ciint br_mdb_add(struct net_device *dev, struct nlattr *tb[], u16 nlmsg_flags,
127062306a36Sopenharmony_ci	       struct netlink_ext_ack *extack)
127162306a36Sopenharmony_ci{
127262306a36Sopenharmony_ci	struct net_bridge_vlan_group *vg;
127362306a36Sopenharmony_ci	struct net_bridge_vlan *v;
127462306a36Sopenharmony_ci	struct br_mdb_config cfg;
127562306a36Sopenharmony_ci	int err;
127662306a36Sopenharmony_ci
127762306a36Sopenharmony_ci	err = br_mdb_config_init(&cfg, dev, tb, nlmsg_flags, extack);
127862306a36Sopenharmony_ci	if (err)
127962306a36Sopenharmony_ci		return err;
128062306a36Sopenharmony_ci
128162306a36Sopenharmony_ci	err = -EINVAL;
128262306a36Sopenharmony_ci	/* host join errors which can happen before creating the group */
128362306a36Sopenharmony_ci	if (!cfg.p && !br_group_is_l2(&cfg.group)) {
128462306a36Sopenharmony_ci		/* don't allow any flags for host-joined IP groups */
128562306a36Sopenharmony_ci		if (cfg.entry->state) {
128662306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Flags are not allowed for host groups");
128762306a36Sopenharmony_ci			goto out;
128862306a36Sopenharmony_ci		}
128962306a36Sopenharmony_ci		if (!br_multicast_is_star_g(&cfg.group)) {
129062306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Groups with sources cannot be manually host joined");
129162306a36Sopenharmony_ci			goto out;
129262306a36Sopenharmony_ci		}
129362306a36Sopenharmony_ci	}
129462306a36Sopenharmony_ci
129562306a36Sopenharmony_ci	if (br_group_is_l2(&cfg.group) && cfg.entry->state != MDB_PERMANENT) {
129662306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Only permanent L2 entries allowed");
129762306a36Sopenharmony_ci		goto out;
129862306a36Sopenharmony_ci	}
129962306a36Sopenharmony_ci
130062306a36Sopenharmony_ci	if (cfg.p) {
130162306a36Sopenharmony_ci		if (cfg.p->state == BR_STATE_DISABLED && cfg.entry->state != MDB_PERMANENT) {
130262306a36Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Port is in disabled state and entry is not permanent");
130362306a36Sopenharmony_ci			goto out;
130462306a36Sopenharmony_ci		}
130562306a36Sopenharmony_ci		vg = nbp_vlan_group(cfg.p);
130662306a36Sopenharmony_ci	} else {
130762306a36Sopenharmony_ci		vg = br_vlan_group(cfg.br);
130862306a36Sopenharmony_ci	}
130962306a36Sopenharmony_ci
131062306a36Sopenharmony_ci	/* If vlan filtering is enabled and VLAN is not specified
131162306a36Sopenharmony_ci	 * install mdb entry on all vlans configured on the port.
131262306a36Sopenharmony_ci	 */
131362306a36Sopenharmony_ci	if (br_vlan_enabled(cfg.br->dev) && vg && cfg.entry->vid == 0) {
131462306a36Sopenharmony_ci		list_for_each_entry(v, &vg->vlan_list, vlist) {
131562306a36Sopenharmony_ci			cfg.entry->vid = v->vid;
131662306a36Sopenharmony_ci			cfg.group.vid = v->vid;
131762306a36Sopenharmony_ci			err = __br_mdb_add(&cfg, extack);
131862306a36Sopenharmony_ci			if (err)
131962306a36Sopenharmony_ci				break;
132062306a36Sopenharmony_ci		}
132162306a36Sopenharmony_ci	} else {
132262306a36Sopenharmony_ci		err = __br_mdb_add(&cfg, extack);
132362306a36Sopenharmony_ci	}
132462306a36Sopenharmony_ci
132562306a36Sopenharmony_ciout:
132662306a36Sopenharmony_ci	br_mdb_config_fini(&cfg);
132762306a36Sopenharmony_ci	return err;
132862306a36Sopenharmony_ci}
132962306a36Sopenharmony_ci
133062306a36Sopenharmony_cistatic int __br_mdb_del(const struct br_mdb_config *cfg)
133162306a36Sopenharmony_ci{
133262306a36Sopenharmony_ci	struct br_mdb_entry *entry = cfg->entry;
133362306a36Sopenharmony_ci	struct net_bridge *br = cfg->br;
133462306a36Sopenharmony_ci	struct net_bridge_mdb_entry *mp;
133562306a36Sopenharmony_ci	struct net_bridge_port_group *p;
133662306a36Sopenharmony_ci	struct net_bridge_port_group __rcu **pp;
133762306a36Sopenharmony_ci	struct br_ip ip = cfg->group;
133862306a36Sopenharmony_ci	int err = -EINVAL;
133962306a36Sopenharmony_ci
134062306a36Sopenharmony_ci	spin_lock_bh(&br->multicast_lock);
134162306a36Sopenharmony_ci	mp = br_mdb_ip_get(br, &ip);
134262306a36Sopenharmony_ci	if (!mp)
134362306a36Sopenharmony_ci		goto unlock;
134462306a36Sopenharmony_ci
134562306a36Sopenharmony_ci	/* host leave */
134662306a36Sopenharmony_ci	if (entry->ifindex == mp->br->dev->ifindex && mp->host_joined) {
134762306a36Sopenharmony_ci		br_multicast_host_leave(mp, false);
134862306a36Sopenharmony_ci		err = 0;
134962306a36Sopenharmony_ci		br_mdb_notify(br->dev, mp, NULL, RTM_DELMDB);
135062306a36Sopenharmony_ci		if (!mp->ports && netif_running(br->dev))
135162306a36Sopenharmony_ci			mod_timer(&mp->timer, jiffies);
135262306a36Sopenharmony_ci		goto unlock;
135362306a36Sopenharmony_ci	}
135462306a36Sopenharmony_ci
135562306a36Sopenharmony_ci	for (pp = &mp->ports;
135662306a36Sopenharmony_ci	     (p = mlock_dereference(*pp, br)) != NULL;
135762306a36Sopenharmony_ci	     pp = &p->next) {
135862306a36Sopenharmony_ci		if (!p->key.port || p->key.port->dev->ifindex != entry->ifindex)
135962306a36Sopenharmony_ci			continue;
136062306a36Sopenharmony_ci
136162306a36Sopenharmony_ci		br_multicast_del_pg(mp, p, pp);
136262306a36Sopenharmony_ci		err = 0;
136362306a36Sopenharmony_ci		break;
136462306a36Sopenharmony_ci	}
136562306a36Sopenharmony_ci
136662306a36Sopenharmony_ciunlock:
136762306a36Sopenharmony_ci	spin_unlock_bh(&br->multicast_lock);
136862306a36Sopenharmony_ci	return err;
136962306a36Sopenharmony_ci}
137062306a36Sopenharmony_ci
137162306a36Sopenharmony_ciint br_mdb_del(struct net_device *dev, struct nlattr *tb[],
137262306a36Sopenharmony_ci	       struct netlink_ext_ack *extack)
137362306a36Sopenharmony_ci{
137462306a36Sopenharmony_ci	struct net_bridge_vlan_group *vg;
137562306a36Sopenharmony_ci	struct net_bridge_vlan *v;
137662306a36Sopenharmony_ci	struct br_mdb_config cfg;
137762306a36Sopenharmony_ci	int err;
137862306a36Sopenharmony_ci
137962306a36Sopenharmony_ci	err = br_mdb_config_init(&cfg, dev, tb, 0, extack);
138062306a36Sopenharmony_ci	if (err)
138162306a36Sopenharmony_ci		return err;
138262306a36Sopenharmony_ci
138362306a36Sopenharmony_ci	if (cfg.p)
138462306a36Sopenharmony_ci		vg = nbp_vlan_group(cfg.p);
138562306a36Sopenharmony_ci	else
138662306a36Sopenharmony_ci		vg = br_vlan_group(cfg.br);
138762306a36Sopenharmony_ci
138862306a36Sopenharmony_ci	/* If vlan filtering is enabled and VLAN is not specified
138962306a36Sopenharmony_ci	 * delete mdb entry on all vlans configured on the port.
139062306a36Sopenharmony_ci	 */
139162306a36Sopenharmony_ci	if (br_vlan_enabled(cfg.br->dev) && vg && cfg.entry->vid == 0) {
139262306a36Sopenharmony_ci		list_for_each_entry(v, &vg->vlan_list, vlist) {
139362306a36Sopenharmony_ci			cfg.entry->vid = v->vid;
139462306a36Sopenharmony_ci			cfg.group.vid = v->vid;
139562306a36Sopenharmony_ci			err = __br_mdb_del(&cfg);
139662306a36Sopenharmony_ci		}
139762306a36Sopenharmony_ci	} else {
139862306a36Sopenharmony_ci		err = __br_mdb_del(&cfg);
139962306a36Sopenharmony_ci	}
140062306a36Sopenharmony_ci
140162306a36Sopenharmony_ci	br_mdb_config_fini(&cfg);
140262306a36Sopenharmony_ci	return err;
140362306a36Sopenharmony_ci}
1404