Merge tag 'rproc-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/andersson...
[linux-2.6-microblaze.git] / net / bridge / br_mdb.c
index 63f9c08..da5ed4c 100644 (file)
@@ -60,6 +60,8 @@ static void __mdb_entry_fill_flags(struct br_mdb_entry *e, unsigned char flags)
        e->flags = 0;
        if (flags & MDB_PG_FLAGS_OFFLOAD)
                e->flags |= MDB_FLAGS_OFFLOAD;
+       if (flags & MDB_PG_FLAGS_FAST_LEAVE)
+               e->flags |= MDB_FLAGS_FAST_LEAVE;
 }
 
 static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip)
@@ -75,6 +77,53 @@ static void __mdb_entry_to_br_ip(struct br_mdb_entry *entry, struct br_ip *ip)
 #endif
 }
 
+static int __mdb_fill_info(struct sk_buff *skb,
+                          struct net_bridge_mdb_entry *mp,
+                          struct net_bridge_port_group *p)
+{
+       struct timer_list *mtimer;
+       struct nlattr *nest_ent;
+       struct br_mdb_entry e;
+       u8 flags = 0;
+       int ifindex;
+
+       memset(&e, 0, sizeof(e));
+       if (p) {
+               ifindex = p->port->dev->ifindex;
+               mtimer = &p->timer;
+               flags = p->flags;
+       } else {
+               ifindex = mp->br->dev->ifindex;
+               mtimer = &mp->timer;
+       }
+
+       __mdb_entry_fill_flags(&e, flags);
+       e.ifindex = ifindex;
+       e.vid = mp->addr.vid;
+       if (mp->addr.proto == htons(ETH_P_IP))
+               e.addr.u.ip4 = mp->addr.u.ip4;
+#if IS_ENABLED(CONFIG_IPV6)
+       if (mp->addr.proto == htons(ETH_P_IPV6))
+               e.addr.u.ip6 = mp->addr.u.ip6;
+#endif
+       e.addr.proto = mp->addr.proto;
+       nest_ent = nla_nest_start_noflag(skb,
+                                        MDBA_MDB_ENTRY_INFO);
+       if (!nest_ent)
+               return -EMSGSIZE;
+
+       if (nla_put_nohdr(skb, sizeof(e), &e) ||
+           nla_put_u32(skb,
+                       MDBA_MDB_EATTR_TIMER,
+                       br_timer_value(mtimer))) {
+               nla_nest_cancel(skb, nest_ent);
+               return -EMSGSIZE;
+       }
+       nla_nest_end(skb, nest_ent);
+
+       return 0;
+}
+
 static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
                            struct net_device *dev)
 {
@@ -93,7 +142,6 @@ static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
        hlist_for_each_entry_rcu(mp, &br->mdb_list, mdb_node) {
                struct net_bridge_port_group *p;
                struct net_bridge_port_group __rcu **pp;
-               struct net_bridge_port *port;
 
                if (idx < s_idx)
                        goto skip;
@@ -104,43 +152,24 @@ static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
                        break;
                }
 
+               if (mp->host_joined) {
+                       err = __mdb_fill_info(skb, mp, NULL);
+                       if (err) {
+                               nla_nest_cancel(skb, nest2);
+                               break;
+                       }
+               }
+
                for (pp = &mp->ports; (p = rcu_dereference(*pp)) != NULL;
                      pp = &p->next) {
-                       struct nlattr *nest_ent;
-                       struct br_mdb_entry e;
-
-                       port = p->port;
-                       if (!port)
+                       if (!p->port)
                                continue;
 
-                       memset(&e, 0, sizeof(e));
-                       e.ifindex = port->dev->ifindex;
-                       e.vid = p->addr.vid;
-                       __mdb_entry_fill_flags(&e, p->flags);
-                       if (p->addr.proto == htons(ETH_P_IP))
-                               e.addr.u.ip4 = p->addr.u.ip4;
-#if IS_ENABLED(CONFIG_IPV6)
-                       if (p->addr.proto == htons(ETH_P_IPV6))
-                               e.addr.u.ip6 = p->addr.u.ip6;
-#endif
-                       e.addr.proto = p->addr.proto;
-                       nest_ent = nla_nest_start_noflag(skb,
-                                                        MDBA_MDB_ENTRY_INFO);
-                       if (!nest_ent) {
-                               nla_nest_cancel(skb, nest2);
-                               err = -EMSGSIZE;
-                               goto out;
-                       }
-                       if (nla_put_nohdr(skb, sizeof(e), &e) ||
-                           nla_put_u32(skb,
-                                       MDBA_MDB_EATTR_TIMER,
-                                       br_timer_value(&p->timer))) {
-                               nla_nest_cancel(skb, nest_ent);
+                       err = __mdb_fill_info(skb, mp, p);
+                       if (err) {
                                nla_nest_cancel(skb, nest2);
-                               err = -EMSGSIZE;
                                goto out;
                        }
-                       nla_nest_end(skb, nest_ent);
                }
                nla_nest_end(skb, nest2);
 skip:
@@ -587,6 +616,19 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port,
                        return err;
        }
 
+       /* host join */
+       if (!port) {
+               /* don't allow any flags for host-joined groups */
+               if (state)
+                       return -EINVAL;
+               if (mp->host_joined)
+                       return -EEXIST;
+
+               br_multicast_host_join(mp, false);
+
+               return 0;
+       }
+
        for (pp = &mp->ports;
             (p = mlock_dereference(*pp, br)) != NULL;
             pp = &p->next) {
@@ -611,19 +653,21 @@ static int __br_mdb_add(struct net *net, struct net_bridge *br,
 {
        struct br_ip ip;
        struct net_device *dev;
-       struct net_bridge_port *p;
+       struct net_bridge_port *p = NULL;
        int ret;
 
        if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
                return -EINVAL;
 
-       dev = __dev_get_by_index(net, entry->ifindex);
-       if (!dev)
-               return -ENODEV;
+       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;
+               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);
 
@@ -638,9 +682,9 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
 {
        struct net *net = sock_net(skb->sk);
        struct net_bridge_vlan_group *vg;
+       struct net_bridge_port *p = NULL;
        struct net_device *dev, *pdev;
        struct br_mdb_entry *entry;
-       struct net_bridge_port *p;
        struct net_bridge_vlan *v;
        struct net_bridge *br;
        int err;
@@ -651,18 +695,22 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh,
 
        br = netdev_priv(dev);
 
+       if (entry->ifindex != br->dev->ifindex) {
+               pdev = __dev_get_by_index(net, entry->ifindex);
+               if (!pdev)
+                       return -ENODEV;
+
+               p = br_port_get_rtnl(pdev);
+               if (!p || p->br != br || p->state == BR_STATE_DISABLED)
+                       return -EINVAL;
+               vg = nbp_vlan_group(p);
+       } else {
+               vg = br_vlan_group(br);
+       }
+
        /* If vlan filtering is enabled and VLAN is not specified
         * install mdb entry on all vlans configured on the port.
         */
-       pdev = __dev_get_by_index(net, entry->ifindex);
-       if (!pdev)
-               return -ENODEV;
-
-       p = br_port_get_rtnl(pdev);
-       if (!p || p->br != br || p->state == BR_STATE_DISABLED)
-               return -EINVAL;
-
-       vg = nbp_vlan_group(p);
        if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
                list_for_each_entry(v, &vg->vlan_list, vlist) {
                        entry->vid = v->vid;
@@ -698,6 +746,15 @@ static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry)
        if (!mp)
                goto unlock;
 
+       /* host leave */
+       if (entry->ifindex == mp->br->dev->ifindex && mp->host_joined) {
+               br_multicast_host_leave(mp, false);
+               err = 0;
+               if (!mp->ports && netif_running(br->dev))
+                       mod_timer(&mp->timer, jiffies);
+               goto unlock;
+       }
+
        for (pp = &mp->ports;
             (p = mlock_dereference(*pp, br)) != NULL;
             pp = &p->next) {
@@ -730,9 +787,9 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
 {
        struct net *net = sock_net(skb->sk);
        struct net_bridge_vlan_group *vg;
+       struct net_bridge_port *p = NULL;
        struct net_device *dev, *pdev;
        struct br_mdb_entry *entry;
-       struct net_bridge_port *p;
        struct net_bridge_vlan *v;
        struct net_bridge *br;
        int err;
@@ -743,18 +800,22 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
 
        br = netdev_priv(dev);
 
+       if (entry->ifindex != br->dev->ifindex) {
+               pdev = __dev_get_by_index(net, entry->ifindex);
+               if (!pdev)
+                       return -ENODEV;
+
+               p = br_port_get_rtnl(pdev);
+               if (!p || p->br != br || p->state == BR_STATE_DISABLED)
+                       return -EINVAL;
+               vg = nbp_vlan_group(p);
+       } else {
+               vg = br_vlan_group(br);
+       }
+
        /* If vlan filtering is enabled and VLAN is not specified
         * delete mdb entry on all vlans configured on the port.
         */
-       pdev = __dev_get_by_index(net, entry->ifindex);
-       if (!pdev)
-               return -ENODEV;
-
-       p = br_port_get_rtnl(pdev);
-       if (!p || p->br != br || p->state == BR_STATE_DISABLED)
-               return -EINVAL;
-
-       vg = nbp_vlan_group(p);
        if (br_vlan_enabled(br->dev) && vg && entry->vid == 0) {
                list_for_each_entry(v, &vg->vlan_list, vlist) {
                        entry->vid = v->vid;