Merge tag 'batadv-net-for-davem-20190509' of git://git.open-mesh.org/linux-merge
[sfrench/cifs-2.6.git] / net / batman-adv / multicast.c
index f91b1b6265cfea6d0f4d056defe30eeed452270e..ec54e236e345432496df8f55b2e00fbad92f3444 100644 (file)
@@ -2,18 +2,6 @@
 /* Copyright (C) 2014-2019  B.A.T.M.A.N. contributors:
  *
  * Linus Lüssing
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of version 2 of the GNU General Public
- * License as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include "multicast.h"
@@ -66,6 +54,7 @@
 #include "hash.h"
 #include "log.h"
 #include "netlink.h"
+#include "send.h"
 #include "soft-interface.h"
 #include "translation-table.h"
 #include "tvlv.h"
@@ -325,8 +314,6 @@ static void batadv_mcast_mla_list_free(struct hlist_head *mcast_list)
  * translation table except the ones listed in the given mcast_list.
  *
  * If mcast_list is NULL then all are retracted.
- *
- * Do not call outside of the mcast worker! (or cancel mcast worker first)
  */
 static void batadv_mcast_mla_tt_retract(struct batadv_priv *bat_priv,
                                        struct hlist_head *mcast_list)
@@ -334,8 +321,6 @@ static void batadv_mcast_mla_tt_retract(struct batadv_priv *bat_priv,
        struct batadv_hw_addr *mcast_entry;
        struct hlist_node *tmp;
 
-       WARN_ON(delayed_work_pending(&bat_priv->mcast.work));
-
        hlist_for_each_entry_safe(mcast_entry, tmp, &bat_priv->mcast.mla_list,
                                  list) {
                if (mcast_list &&
@@ -359,8 +344,6 @@ static void batadv_mcast_mla_tt_retract(struct batadv_priv *bat_priv,
  *
  * Adds multicast listener announcements from the given mcast_list to the
  * translation table if they have not been added yet.
- *
- * Do not call outside of the mcast worker! (or cancel mcast worker first)
  */
 static void batadv_mcast_mla_tt_add(struct batadv_priv *bat_priv,
                                    struct hlist_head *mcast_list)
@@ -368,8 +351,6 @@ static void batadv_mcast_mla_tt_add(struct batadv_priv *bat_priv,
        struct batadv_hw_addr *mcast_entry;
        struct hlist_node *tmp;
 
-       WARN_ON(delayed_work_pending(&bat_priv->mcast.work));
-
        if (!mcast_list)
                return;
 
@@ -658,7 +639,10 @@ static void batadv_mcast_mla_update(struct work_struct *work)
        priv_mcast = container_of(delayed_work, struct batadv_priv_mcast, work);
        bat_priv = container_of(priv_mcast, struct batadv_priv, mcast);
 
+       spin_lock(&bat_priv->mcast.mla_lock);
        __batadv_mcast_mla_update(bat_priv);
+       spin_unlock(&bat_priv->mcast.mla_lock);
+
        batadv_mcast_start_timer(bat_priv);
 }
 
@@ -991,6 +975,7 @@ batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb,
 {
        int ret, tt_count, ip_count, unsnoop_count, total_count;
        bool is_unsnoopable = false;
+       unsigned int mcast_fanout;
        struct ethhdr *ethhdr;
 
        ret = batadv_mcast_forw_mode_check(bat_priv, skb, &is_unsnoopable);
@@ -1025,8 +1010,203 @@ batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb,
        case 0:
                return BATADV_FORW_NONE;
        default:
-               return BATADV_FORW_ALL;
+               mcast_fanout = atomic_read(&bat_priv->multicast_fanout);
+
+               if (!unsnoop_count && total_count <= mcast_fanout)
+                       return BATADV_FORW_SOME;
+       }
+
+       return BATADV_FORW_ALL;
+}
+
+/**
+ * batadv_mcast_forw_tt() - forwards a packet to multicast listeners
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: the multicast packet to transmit
+ * @vid: the vlan identifier
+ *
+ * Sends copies of a frame with multicast destination to any multicast
+ * listener registered in the translation table. A transmission is performed
+ * via a batman-adv unicast packet for each such destination node.
+ *
+ * Return: NET_XMIT_DROP on memory allocation failure, NET_XMIT_SUCCESS
+ * otherwise.
+ */
+static int
+batadv_mcast_forw_tt(struct batadv_priv *bat_priv, struct sk_buff *skb,
+                    unsigned short vid)
+{
+       int ret = NET_XMIT_SUCCESS;
+       struct sk_buff *newskb;
+
+       struct batadv_tt_orig_list_entry *orig_entry;
+
+       struct batadv_tt_global_entry *tt_global;
+       const u8 *addr = eth_hdr(skb)->h_dest;
+
+       tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid);
+       if (!tt_global)
+               goto out;
+
+       rcu_read_lock();
+       hlist_for_each_entry_rcu(orig_entry, &tt_global->orig_list, list) {
+               newskb = skb_copy(skb, GFP_ATOMIC);
+               if (!newskb) {
+                       ret = NET_XMIT_DROP;
+                       break;
+               }
+
+               batadv_send_skb_unicast(bat_priv, newskb, BATADV_UNICAST, 0,
+                                       orig_entry->orig_node, vid);
+       }
+       rcu_read_unlock();
+
+       batadv_tt_global_entry_put(tt_global);
+
+out:
+       return ret;
+}
+
+/**
+ * batadv_mcast_forw_want_all_ipv4() - forward to nodes with want-all-ipv4
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: the multicast packet to transmit
+ * @vid: the vlan identifier
+ *
+ * Sends copies of a frame with multicast destination to any node with a
+ * BATADV_MCAST_WANT_ALL_IPV4 flag set. A transmission is performed via a
+ * batman-adv unicast packet for each such destination node.
+ *
+ * Return: NET_XMIT_DROP on memory allocation failure, NET_XMIT_SUCCESS
+ * otherwise.
+ */
+static int
+batadv_mcast_forw_want_all_ipv4(struct batadv_priv *bat_priv,
+                               struct sk_buff *skb, unsigned short vid)
+{
+       struct batadv_orig_node *orig_node;
+       int ret = NET_XMIT_SUCCESS;
+       struct sk_buff *newskb;
+
+       rcu_read_lock();
+       hlist_for_each_entry_rcu(orig_node,
+                                &bat_priv->mcast.want_all_ipv4_list,
+                                mcast_want_all_ipv4_node) {
+               newskb = skb_copy(skb, GFP_ATOMIC);
+               if (!newskb) {
+                       ret = NET_XMIT_DROP;
+                       break;
+               }
+
+               batadv_send_skb_unicast(bat_priv, newskb, BATADV_UNICAST, 0,
+                                       orig_node, vid);
        }
+       rcu_read_unlock();
+       return ret;
+}
+
+/**
+ * batadv_mcast_forw_want_all_ipv6() - forward to nodes with want-all-ipv6
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: The multicast packet to transmit
+ * @vid: the vlan identifier
+ *
+ * Sends copies of a frame with multicast destination to any node with a
+ * BATADV_MCAST_WANT_ALL_IPV6 flag set. A transmission is performed via a
+ * batman-adv unicast packet for each such destination node.
+ *
+ * Return: NET_XMIT_DROP on memory allocation failure, NET_XMIT_SUCCESS
+ * otherwise.
+ */
+static int
+batadv_mcast_forw_want_all_ipv6(struct batadv_priv *bat_priv,
+                               struct sk_buff *skb, unsigned short vid)
+{
+       struct batadv_orig_node *orig_node;
+       int ret = NET_XMIT_SUCCESS;
+       struct sk_buff *newskb;
+
+       rcu_read_lock();
+       hlist_for_each_entry_rcu(orig_node,
+                                &bat_priv->mcast.want_all_ipv6_list,
+                                mcast_want_all_ipv6_node) {
+               newskb = skb_copy(skb, GFP_ATOMIC);
+               if (!newskb) {
+                       ret = NET_XMIT_DROP;
+                       break;
+               }
+
+               batadv_send_skb_unicast(bat_priv, newskb, BATADV_UNICAST, 0,
+                                       orig_node, vid);
+       }
+       rcu_read_unlock();
+       return ret;
+}
+
+/**
+ * batadv_mcast_forw_want_all() - forward packet to nodes in a want-all list
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: the multicast packet to transmit
+ * @vid: the vlan identifier
+ *
+ * Sends copies of a frame with multicast destination to any node with a
+ * BATADV_MCAST_WANT_ALL_IPV4 or BATADV_MCAST_WANT_ALL_IPV6 flag set. A
+ * transmission is performed via a batman-adv unicast packet for each such
+ * destination node.
+ *
+ * Return: NET_XMIT_DROP on memory allocation failure or if the protocol family
+ * is neither IPv4 nor IPv6. NET_XMIT_SUCCESS otherwise.
+ */
+static int
+batadv_mcast_forw_want_all(struct batadv_priv *bat_priv,
+                          struct sk_buff *skb, unsigned short vid)
+{
+       switch (ntohs(eth_hdr(skb)->h_proto)) {
+       case ETH_P_IP:
+               return batadv_mcast_forw_want_all_ipv4(bat_priv, skb, vid);
+       case ETH_P_IPV6:
+               return batadv_mcast_forw_want_all_ipv6(bat_priv, skb, vid);
+       default:
+               /* we shouldn't be here... */
+               return NET_XMIT_DROP;
+       }
+}
+
+/**
+ * batadv_mcast_forw_send() - send packet to any detected multicast recpient
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: the multicast packet to transmit
+ * @vid: the vlan identifier
+ *
+ * Sends copies of a frame with multicast destination to any node that signaled
+ * interest in it, that is either via the translation table or the according
+ * want-all flags. A transmission is performed via a batman-adv unicast packet
+ * for each such destination node.
+ *
+ * The given skb is consumed/freed.
+ *
+ * Return: NET_XMIT_DROP on memory allocation failure or if the protocol family
+ * is neither IPv4 nor IPv6. NET_XMIT_SUCCESS otherwise.
+ */
+int batadv_mcast_forw_send(struct batadv_priv *bat_priv, struct sk_buff *skb,
+                          unsigned short vid)
+{
+       int ret;
+
+       ret = batadv_mcast_forw_tt(bat_priv, skb, vid);
+       if (ret != NET_XMIT_SUCCESS) {
+               kfree_skb(skb);
+               return ret;
+       }
+
+       ret = batadv_mcast_forw_want_all(bat_priv, skb, vid);
+       if (ret != NET_XMIT_SUCCESS) {
+               kfree_skb(skb);
+               return ret;
+       }
+
+       consume_skb(skb);
+       return ret;
 }
 
 /**