net: bridge: mdb: move all port and bridge checks to br_mdb_add
[linux-2.6-block.git] / net / bridge / br_mdb.c
index da5ed4cf92333b6a61bc775e7a9bf0678c6826c6..92ab7369fee058d7be5b34fcb0fb98211f22840c 100644 (file)
@@ -77,10 +77,67 @@ static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip)
 #endif
 }
 
+static int __mdb_fill_srcs(struct sk_buff *skb,
+                          struct net_bridge_port_group *p)
+{
+       struct net_bridge_group_src *ent;
+       struct nlattr *nest, *nest_ent;
+
+       if (hlist_empty(&p->src_list))
+               return 0;
+
+       nest = nla_nest_start(skb, MDBA_MDB_EATTR_SRC_LIST);
+       if (!nest)
+               return -EMSGSIZE;
+
+       hlist_for_each_entry_rcu(ent, &p->src_list, node,
+                                lockdep_is_held(&p->port->br->multicast_lock)) {
+               nest_ent = nla_nest_start(skb, MDBA_MDB_SRCLIST_ENTRY);
+               if (!nest_ent)
+                       goto out_cancel_err;
+               switch (ent->addr.proto) {
+               case htons(ETH_P_IP):
+                       if (nla_put_in_addr(skb, MDBA_MDB_SRCATTR_ADDRESS,
+                                           ent->addr.u.ip4)) {
+                               nla_nest_cancel(skb, nest_ent);
+                               goto out_cancel_err;
+                       }
+                       break;
+#if IS_ENABLED(CONFIG_IPV6)
+               case htons(ETH_P_IPV6):
+                       if (nla_put_in6_addr(skb, MDBA_MDB_SRCATTR_ADDRESS,
+                                            &ent->addr.u.ip6)) {
+                               nla_nest_cancel(skb, nest_ent);
+                               goto out_cancel_err;
+                       }
+                       break;
+#endif
+               default:
+                       nla_nest_cancel(skb, nest_ent);
+                       continue;
+               }
+               if (nla_put_u32(skb, MDBA_MDB_SRCATTR_TIMER,
+                               br_timer_value(&ent->timer))) {
+                       nla_nest_cancel(skb, nest_ent);
+                       goto out_cancel_err;
+               }
+               nla_nest_end(skb, nest_ent);
+       }
+
+       nla_nest_end(skb, nest);
+
+       return 0;
+
+out_cancel_err:
+       nla_nest_cancel(skb, nest);
+       return -EMSGSIZE;
+}
+
 static int __mdb_fill_info(struct sk_buff *skb,
                           struct net_bridge_mdb_entry *mp,
                           struct net_bridge_port_group *p)
 {
+       bool dump_srcs_mode = false;
        struct timer_list *mtimer;
        struct nlattr *nest_ent;
        struct br_mdb_entry e;
@@ -119,6 +176,23 @@ static int __mdb_fill_info(struct sk_buff *skb,
                nla_nest_cancel(skb, nest_ent);
                return -EMSGSIZE;
        }
+       switch (mp->addr.proto) {
+       case htons(ETH_P_IP):
+               dump_srcs_mode = !!(p && mp->br->multicast_igmp_version == 3);
+               break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case htons(ETH_P_IPV6):
+               dump_srcs_mode = !!(p && mp->br->multicast_mld_version == 2);
+               break;
+#endif
+       }
+       if (dump_srcs_mode &&
+           (__mdb_fill_srcs(skb, p) ||
+            nla_put_u8(skb, MDBA_MDB_EATTR_GROUP_MODE, p->filter_mode))) {
+               nla_nest_cancel(skb, nest_ent);
+               return -EMSGSIZE;
+       }
+
        nla_nest_end(skb, nest_ent);
 
        return 0;
@@ -127,7 +201,7 @@ static int __mdb_fill_info(struct sk_buff *skb,
 static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
                            struct net_device *dev)
 {
-       int idx = 0, s_idx = cb->args[1], err = 0;
+       int idx = 0, s_idx = cb->args[1], err = 0, pidx = 0, s_pidx = cb->args[2];
        struct net_bridge *br = netdev_priv(dev);
        struct net_bridge_mdb_entry *mp;
        struct nlattr *nest, *nest2;
@@ -152,7 +226,7 @@ static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
                        break;
                }
 
-               if (mp->host_joined) {
+               if (!s_pidx && mp->host_joined) {
                        err = __mdb_fill_info(skb, mp, NULL);
                        if (err) {
                                nla_nest_cancel(skb, nest2);
@@ -164,13 +238,19 @@ static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
                      pp = &p->next) {
                        if (!p->port)
                                continue;
+                       if (pidx < s_pidx)
+                               goto skip_pg;
 
                        err = __mdb_fill_info(skb, mp, p);
                        if (err) {
-                               nla_nest_cancel(skb, nest2);
+                               nla_nest_end(skb, nest2);
                                goto out;
                        }
+skip_pg:
+                       pidx++;
                }
+               pidx = 0;
+               s_pidx = 0;
                nla_nest_end(skb, nest2);
 skip:
                idx++;
@@ -178,6 +258,7 @@ skip:
 
 out:
        cb->args[1] = idx;
+       cb->args[2] = pidx;
        nla_nest_end(skb, nest);
        return err;
 }
@@ -263,14 +344,15 @@ out:
 
 static int nlmsg_populate_mdb_fill(struct sk_buff *skb,
                                   struct net_device *dev,
-                                  struct br_mdb_entry *entry, u32 pid,
-                                  u32 seq, int type, unsigned int flags)
+                                  struct net_bridge_mdb_entry *mp,
+                                  struct net_bridge_port_group *pg,
+                                  int type)
 {
        struct nlmsghdr *nlh;
        struct br_port_msg *bpm;
        struct nlattr *nest, *nest2;
 
-       nlh = nlmsg_put(skb, pid, seq, type, sizeof(*bpm), 0);
+       nlh = nlmsg_put(skb, 0, 0, type, sizeof(*bpm), 0);
        if (!nlh)
                return -EMSGSIZE;
 
@@ -285,7 +367,7 @@ static int nlmsg_populate_mdb_fill(struct sk_buff *skb,
        if (nest2 == NULL)
                goto end;
 
-       if (nla_put(skb, MDBA_MDB_ENTRY_INFO, sizeof(*entry), entry))
+       if (__mdb_fill_info(skb, mp, pg))
                goto end;
 
        nla_nest_end(skb, nest2);
@@ -300,10 +382,49 @@ cancel:
        return -EMSGSIZE;
 }
 
-static inline size_t rtnl_mdb_nlmsg_size(void)
+static size_t rtnl_mdb_nlmsg_size(struct net_bridge_port_group *pg)
 {
-       return NLMSG_ALIGN(sizeof(struct br_port_msg))
-               + nla_total_size(sizeof(struct br_mdb_entry));
+       size_t nlmsg_size = NLMSG_ALIGN(sizeof(struct br_port_msg)) +
+                           nla_total_size(sizeof(struct br_mdb_entry)) +
+                           nla_total_size(sizeof(u32));
+       struct net_bridge_group_src *ent;
+       size_t addr_size = 0;
+
+       if (!pg)
+               goto out;
+
+       switch (pg->addr.proto) {
+       case htons(ETH_P_IP):
+               if (pg->port->br->multicast_igmp_version == 2)
+                       goto out;
+               addr_size = sizeof(__be32);
+               break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case htons(ETH_P_IPV6):
+               if (pg->port->br->multicast_mld_version == 1)
+                       goto out;
+               addr_size = sizeof(struct in6_addr);
+               break;
+#endif
+       }
+
+       /* MDBA_MDB_EATTR_GROUP_MODE */
+       nlmsg_size += nla_total_size(sizeof(u8));
+
+       /* MDBA_MDB_EATTR_SRC_LIST nested attr */
+       if (!hlist_empty(&pg->src_list))
+               nlmsg_size += nla_total_size(0);
+
+       hlist_for_each_entry(ent, &pg->src_list, node) {
+               /* MDBA_MDB_SRCLIST_ENTRY nested attr +
+                * MDBA_MDB_SRCATTR_ADDRESS + MDBA_MDB_SRCATTR_TIMER
+                */
+               nlmsg_size += nla_total_size(0) +
+                             nla_total_size(addr_size) +
+                             nla_total_size(sizeof(u32));
+       }
+out:
+       return nlmsg_size;
 }
 
 struct br_mdb_complete_info {
@@ -341,21 +462,22 @@ err:
 
 static void br_mdb_switchdev_host_port(struct net_device *dev,
                                       struct net_device *lower_dev,
-                                      struct br_mdb_entry *entry, int type)
+                                      struct net_bridge_mdb_entry *mp,
+                                      int type)
 {
        struct switchdev_obj_port_mdb mdb = {
                .obj = {
                        .id = SWITCHDEV_OBJ_ID_HOST_MDB,
                        .flags = SWITCHDEV_F_DEFER,
                },
-               .vid = entry->vid,
+               .vid = mp->addr.vid,
        };
 
-       if (entry->addr.proto == htons(ETH_P_IP))
-               ip_eth_mc_map(entry->addr.u.ip4, mdb.addr);
+       if (mp->addr.proto == htons(ETH_P_IP))
+               ip_eth_mc_map(mp->addr.u.ip4, mdb.addr);
 #if IS_ENABLED(CONFIG_IPV6)
        else
-               ipv6_eth_mc_map(&entry->addr.u.ip6, mdb.addr);
+               ipv6_eth_mc_map(&mp->addr.u.ip6, mdb.addr);
 #endif
 
        mdb.obj.orig_dev = dev;
@@ -370,17 +492,19 @@ static void br_mdb_switchdev_host_port(struct net_device *dev,
 }
 
 static void br_mdb_switchdev_host(struct net_device *dev,
-                                 struct br_mdb_entry *entry, int type)
+                                 struct net_bridge_mdb_entry *mp, int type)
 {
        struct net_device *lower_dev;
        struct list_head *iter;
 
        netdev_for_each_lower_dev(dev, lower_dev, iter)
-               br_mdb_switchdev_host_port(dev, lower_dev, entry, type);
+               br_mdb_switchdev_host_port(dev, lower_dev, mp, type);
 }
 
-static void __br_mdb_notify(struct net_device *dev, struct net_bridge_port *p,
-                           struct br_mdb_entry *entry, int type)
+void br_mdb_notify(struct net_device *dev,
+                  struct net_bridge_mdb_entry *mp,
+                  struct net_bridge_port_group *pg,
+                  int type)
 {
        struct br_mdb_complete_info *complete_info;
        struct switchdev_obj_port_mdb mdb = {
@@ -388,44 +512,45 @@ static void __br_mdb_notify(struct net_device *dev, struct net_bridge_port *p,
                        .id = SWITCHDEV_OBJ_ID_PORT_MDB,
                        .flags = SWITCHDEV_F_DEFER,
                },
-               .vid = entry->vid,
+               .vid = mp->addr.vid,
        };
-       struct net_device *port_dev;
        struct net *net = dev_net(dev);
        struct sk_buff *skb;
        int err = -ENOBUFS;
 
-       port_dev = __dev_get_by_index(net, entry->ifindex);
-       if (entry->addr.proto == htons(ETH_P_IP))
-               ip_eth_mc_map(entry->addr.u.ip4, mdb.addr);
+       if (pg) {
+               if (mp->addr.proto == htons(ETH_P_IP))
+                       ip_eth_mc_map(mp->addr.u.ip4, mdb.addr);
 #if IS_ENABLED(CONFIG_IPV6)
-       else
-               ipv6_eth_mc_map(&entry->addr.u.ip6, mdb.addr);
+               else
+                       ipv6_eth_mc_map(&mp->addr.u.ip6, mdb.addr);
 #endif
-
-       mdb.obj.orig_dev = port_dev;
-       if (p && port_dev && type == RTM_NEWMDB) {
-               complete_info = kmalloc(sizeof(*complete_info), GFP_ATOMIC);
-               if (complete_info) {
-                       complete_info->port = p;
-                       __mdb_entry_to_br_ip(entry, &complete_info->ip);
+               mdb.obj.orig_dev = pg->port->dev;
+               switch (type) {
+               case RTM_NEWMDB:
+                       complete_info = kmalloc(sizeof(*complete_info), GFP_ATOMIC);
+                       if (!complete_info)
+                               break;
+                       complete_info->port = pg->port;
+                       complete_info->ip = mp->addr;
                        mdb.obj.complete_priv = complete_info;
                        mdb.obj.complete = br_mdb_complete;
-                       if (switchdev_port_obj_add(port_dev, &mdb.obj, NULL))
+                       if (switchdev_port_obj_add(pg->port->dev, &mdb.obj, NULL))
                                kfree(complete_info);
+                       break;
+               case RTM_DELMDB:
+                       switchdev_port_obj_del(pg->port->dev, &mdb.obj);
+                       break;
                }
-       } else if (p && port_dev && type == RTM_DELMDB) {
-               switchdev_port_obj_del(port_dev, &mdb.obj);
+       } else {
+               br_mdb_switchdev_host(dev, mp, type);
        }
 
-       if (!p)
-               br_mdb_switchdev_host(dev, entry, type);
-
-       skb = nlmsg_new(rtnl_mdb_nlmsg_size(), GFP_ATOMIC);
+       skb = nlmsg_new(rtnl_mdb_nlmsg_size(pg), GFP_ATOMIC);
        if (!skb)
                goto errout;
 
-       err = nlmsg_populate_mdb_fill(skb, dev, entry, 0, 0, type, NTF_SELF);
+       err = nlmsg_populate_mdb_fill(skb, dev, mp, pg, type);
        if (err < 0) {
                kfree_skb(skb);
                goto errout;
@@ -437,26 +562,6 @@ errout:
        rtnl_set_sk_err(net, RTNLGRP_MDB, err);
 }
 
-void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port,
-                  struct br_ip *group, int type, u8 flags)
-{
-       struct br_mdb_entry entry;
-
-       memset(&entry, 0, sizeof(entry));
-       if (port)
-               entry.ifindex = port->dev->ifindex;
-       else
-               entry.ifindex = dev->ifindex;
-       entry.addr.proto = group->proto;
-       entry.addr.u.ip4 = group->u.ip4;
-#if IS_ENABLED(CONFIG_IPV6)
-       entry.addr.u.ip6 = group->u.ip6;
-#endif
-       entry.vid = group->vid;
-       __mdb_entry_fill_flags(&entry, flags);
-       __br_mdb_notify(dev, port, &entry, type);
-}
-
 static int nlmsg_populate_rtr_fill(struct sk_buff *skb,
                                   struct net_device *dev,
                                   int ifindex, u32 pid,
@@ -524,33 +629,50 @@ errout:
        rtnl_set_sk_err(net, RTNLGRP_MDB, err);
 }
 
-static bool is_valid_mdb_entry(struct br_mdb_entry *entry)
+static bool is_valid_mdb_entry(struct br_mdb_entry *entry,
+                              struct netlink_ext_ack *extack)
 {
-       if (entry->ifindex == 0)
+       if (entry->ifindex == 0) {
+               NL_SET_ERR_MSG_MOD(extack, "Zero entry ifindex is not allowed");
                return false;
+       }
 
        if (entry->addr.proto == htons(ETH_P_IP)) {
-               if (!ipv4_is_multicast(entry->addr.u.ip4))
+               if (!ipv4_is_multicast(entry->addr.u.ip4)) {
+                       NL_SET_ERR_MSG_MOD(extack, "IPv4 entry group address is not multicast");
                        return false;
-               if (ipv4_is_local_multicast(entry->addr.u.ip4))
+               }
+               if (ipv4_is_local_multicast(entry->addr.u.ip4)) {
+                       NL_SET_ERR_MSG_MOD(extack, "IPv4 entry group address is local multicast");
                        return false;
+               }
 #if IS_ENABLED(CONFIG_IPV6)
        } else if (entry->addr.proto == htons(ETH_P_IPV6)) {
-               if (ipv6_addr_is_ll_all_nodes(&entry->addr.u.ip6))
+               if (ipv6_addr_is_ll_all_nodes(&entry->addr.u.ip6)) {
+                       NL_SET_ERR_MSG_MOD(extack, "IPv6 entry group address is link-local all nodes");
                        return false;
+               }
 #endif
-       } else
+       } else {
+               NL_SET_ERR_MSG_MOD(extack, "Unknown entry protocol");
                return false;
-       if (entry->state != MDB_PERMANENT && entry->state != MDB_TEMPORARY)
+       }
+
+       if (entry->state != MDB_PERMANENT && entry->state != MDB_TEMPORARY) {
+               NL_SET_ERR_MSG_MOD(extack, "Unknown entry state");
                return false;
-       if (entry->vid >= VLAN_VID_MASK)
+       }
+       if (entry->vid >= VLAN_VID_MASK) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid entry VLAN id");
                return false;
+       }
 
        return true;
 }
 
 static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh,
-                       struct net_device **pdev, struct br_mdb_entry **pentry)
+                       struct net_device **pdev, struct br_mdb_entry **pentry,
+                       struct netlink_ext_ack *extack)
 {
        struct net *net = sock_net(skb->sk);
        struct br_mdb_entry *entry;
@@ -566,41 +688,42 @@ static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh,
 
        bpm = nlmsg_data(nlh);
        if (bpm->ifindex == 0) {
-               pr_info("PF_BRIDGE: br_mdb_parse() with invalid ifindex\n");
+               NL_SET_ERR_MSG_MOD(extack, "Invalid bridge ifindex");
                return -EINVAL;
        }
 
        dev = __dev_get_by_index(net, bpm->ifindex);
        if (dev == NULL) {
-               pr_info("PF_BRIDGE: br_mdb_parse() with unknown ifindex\n");
+               NL_SET_ERR_MSG_MOD(extack, "Bridge device doesn't exist");
                return -ENODEV;
        }
 
        if (!(dev->priv_flags & IFF_EBRIDGE)) {
-               pr_info("PF_BRIDGE: br_mdb_parse() with non-bridge\n");
+               NL_SET_ERR_MSG_MOD(extack, "Device is not a bridge");
                return -EOPNOTSUPP;
        }
 
        *pdev = dev;
 
-       if (!tb[MDBA_SET_ENTRY] ||
-           nla_len(tb[MDBA_SET_ENTRY]) != sizeof(struct br_mdb_entry)) {
-               pr_info("PF_BRIDGE: br_mdb_parse() with invalid attr\n");
+       if (!tb[MDBA_SET_ENTRY]) {
+               NL_SET_ERR_MSG_MOD(extack, "Missing MDBA_SET_ENTRY attribute");
                return -EINVAL;
        }
-
-       entry = nla_data(tb[MDBA_SET_ENTRY]);
-       if (!is_valid_mdb_entry(entry)) {
-               pr_info("PF_BRIDGE: br_mdb_parse() with invalid entry\n");
+       if (nla_len(tb[MDBA_SET_ENTRY]) != sizeof(struct br_mdb_entry)) {
+               NL_SET_ERR_MSG_MOD(extack, "Invalid MDBA_SET_ENTRY attribute length");
                return -EINVAL;
        }
 
+       entry = nla_data(tb[MDBA_SET_ENTRY]);
+       if (!is_valid_mdb_entry(entry, extack))
+               return -EINVAL;
        *pentry = entry;
+
        return 0;
 }
 
 static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
-                           struct br_ip *group, unsigned char state)
+                           struct br_ip *group, struct br_mdb_entry *entry)
 {
        struct net_bridge_mdb_entry *mp;
        struct net_bridge_port_group *p;
@@ -619,12 +742,13 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
        /* host join */
        if (!port) {
                /* don't allow any flags for host-joined groups */
-               if (state)
+               if (entry->state)
                        return -EINVAL;
                if (mp->host_joined)
                        return -EEXIST;
 
                br_multicast_host_join(mp, false);
+               br_mdb_notify(br->dev, mp, NULL, RTM_NEWMDB);
 
                return 0;
        }
@@ -638,42 +762,31 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
                        break;
        }
 
-       p = br_multicast_new_port_group(port, group, *pp, state, NULL);
+       p = br_multicast_new_port_group(port, group, *pp, entry->state, NULL,
+                                       MCAST_EXCLUDE);
        if (unlikely(!p))
                return -ENOMEM;
        rcu_assign_pointer(*pp, p);
-       if (state == MDB_TEMPORARY)
+       if (entry->state == MDB_TEMPORARY)
                mod_timer(&p->timer, now + br->multicast_membership_interval);
+       br_mdb_notify(br->dev, mp, p, RTM_NEWMDB);
 
        return 0;
 }
 
 static int __br_mdb_add(struct net *net, struct net_bridge *br,
+                       struct net_bridge_port *p,
                        struct br_mdb_entry *entry)
 {
        struct br_ip ip;
-       struct net_device *dev;
-       struct net_bridge_port *p = NULL;
        int ret;
 
-       if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
-               return -EINVAL;
-
-       if (entry->ifindex != br->dev->ifindex) {
-               dev = __dev_get_by_index(net, entry->ifindex);
-               if (!dev)
-                       return -ENODEV;
-
-               p = br_port_get_rtnl(dev);
-               if (!p || p->br != br || p->state == BR_STATE_DISABLED)
-                       return -EINVAL;
-       }
-
        __mdb_entry_to_br_ip(entry, &ip);
 
        spin_lock_bh(&br->multicast_lock);
-       ret = br_mdb_add_group(br, p, &ip, entry->state);
+       ret = br_mdb_add_group(br, p, &ip, entry);
        spin_unlock_bh(&br->multicast_lock);
+
        return ret;
 }
 
@@ -689,12 +802,15 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
        struct net_bridge *br;
        int err;
 
-       err = br_mdb_parse(skb, nlh, &dev, &entry);
+       err = br_mdb_parse(skb, nlh, &dev, &entry, extack);
        if (err < 0)
                return err;
 
        br = netdev_priv(dev);
 
+       if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
+               return -EINVAL;
+
        if (entry->ifindex != br->dev->ifindex) {
                pdev = __dev_get_by_index(net, entry->ifindex);
                if (!pdev)
@@ -714,15 +830,12 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
        if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
                list_for_each_entry(v, &vg->vlan_list, vlist) {
                        entry->vid = v->vid;
-                       err = __br_mdb_add(net, br, entry);
+                       err = __br_mdb_add(net, br, p, entry);
                        if (err)
                                break;
-                       __br_mdb_notify(dev, p, entry, RTM_NEWMDB);
                }
        } else {
-               err = __br_mdb_add(net, br, entry);
-               if (!err)
-                       __br_mdb_notify(dev, p, entry, RTM_NEWMDB);
+               err = __br_mdb_add(net, br, p, entry);
        }
 
        return err;
@@ -750,6 +863,7 @@ static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry)
        if (entry->ifindex == mp->br->dev->ifindex && mp->host_joined) {
                br_multicast_host_leave(mp, false);
                err = 0;
+               br_mdb_notify(br->dev, mp, NULL, RTM_DELMDB);
                if (!mp->ports && netif_running(br->dev))
                        mod_timer(&mp->timer, jiffies);
                goto unlock;
@@ -764,16 +878,8 @@ static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry)
                if (p->port->state == BR_STATE_DISABLED)
                        goto unlock;
 
-               __mdb_entry_fill_flags(entry, p->flags);
-               rcu_assign_pointer(*pp, p->next);
-               hlist_del_init(&p->mglist);
-               del_timer(&p->timer);
-               kfree_rcu(p, rcu);
+               br_multicast_del_pg(mp, p, pp);
                err = 0;
-
-               if (!mp->ports && !mp->host_joined &&
-                   netif_running(br->dev))
-                       mod_timer(&mp->timer, jiffies);
                break;
        }
 
@@ -794,7 +900,7 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
        struct net_bridge *br;
        int err;
 
-       err = br_mdb_parse(skb, nlh, &dev, &entry);
+       err = br_mdb_parse(skb, nlh, &dev, &entry, extack);
        if (err < 0)
                return err;
 
@@ -820,13 +926,9 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
                list_for_each_entry(v, &vg->vlan_list, vlist) {
                        entry->vid = v->vid;
                        err = __br_mdb_del(br, entry);
-                       if (!err)
-                               __br_mdb_notify(dev, p, entry, RTM_DELMDB);
                }
        } else {
                err = __br_mdb_del(br, entry);
-               if (!err)
-                       __br_mdb_notify(dev, p, entry, RTM_DELMDB);
        }
 
        return err;