is used to classify clients as "isolated" by the
                Extended Isolation feature.
 
+What:           /sys/class/net/<mesh_iface>/mesh/multicast_mode
+Date:           Feb 2014
+Contact:        Linus Lüssing <linus.luessing@web.de>
+Description:
+                Indicates whether multicast optimizations are enabled
+                or disabled. If set to zero then all nodes in the
+                mesh are going to use classic flooding for any
+                multicast packet with no optimizations.
+
 What:           /sys/class/net/<mesh_iface>/mesh/network_coding
 Date:           Nov 2012
 Contact:        Martin Hundeboll <martin@hundeboll.net>
 
 #include "originator.h"
 #include "hard-interface.h"
 #include "translation-table.h"
+#include "multicast.h"
 
 /**
  * batadv_mcast_mla_softif_get - get softif multicast listeners
        batadv_mcast_mla_list_free(&mcast_list);
 }
 
+/**
+ * batadv_mcast_forw_mode_check_ipv6 - check for optimized forwarding potential
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: the IPv6 packet to check
+ *
+ * Checks whether the given IPv6 packet has the potential to be forwarded with a
+ * mode more optimal than classic flooding.
+ *
+ * If so then returns 0. Otherwise -EINVAL is returned or -ENOMEM if we are out
+ * of memory.
+ */
+static int batadv_mcast_forw_mode_check_ipv6(struct batadv_priv *bat_priv,
+                                            struct sk_buff *skb)
+{
+       struct ipv6hdr *ip6hdr;
+
+       /* We might fail due to out-of-memory -> drop it */
+       if (!pskb_may_pull(skb, sizeof(struct ethhdr) + sizeof(*ip6hdr)))
+               return -ENOMEM;
+
+       ip6hdr = ipv6_hdr(skb);
+
+       /* TODO: Implement Multicast Router Discovery (RFC4286),
+        * then allow scope > link local, too
+        */
+       if (IPV6_ADDR_MC_SCOPE(&ip6hdr->daddr) != IPV6_ADDR_SCOPE_LINKLOCAL)
+               return -EINVAL;
+
+       /* link-local-all-nodes multicast listeners behind a bridge are
+        * not snoopable (see RFC4541, section 3, paragraph 3)
+        */
+       if (ipv6_addr_is_ll_all_nodes(&ip6hdr->daddr))
+               return -EINVAL;
+
+       return 0;
+}
+
+/**
+ * batadv_mcast_forw_mode_check - check for optimized forwarding potential
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: the multicast frame to check
+ *
+ * Checks whether the given multicast ethernet frame has the potential to be
+ * forwarded with a mode more optimal than classic flooding.
+ *
+ * If so then returns 0. Otherwise -EINVAL is returned or -ENOMEM if we are out
+ * of memory.
+ */
+static int batadv_mcast_forw_mode_check(struct batadv_priv *bat_priv,
+                                       struct sk_buff *skb)
+{
+       struct ethhdr *ethhdr = eth_hdr(skb);
+
+       if (!atomic_read(&bat_priv->multicast_mode))
+               return -EINVAL;
+
+       if (atomic_read(&bat_priv->mcast.num_disabled))
+               return -EINVAL;
+
+       switch (ntohs(ethhdr->h_proto)) {
+       case ETH_P_IPV6:
+               return batadv_mcast_forw_mode_check_ipv6(bat_priv, skb);
+       default:
+               return -EINVAL;
+       }
+}
+
+/**
+ * batadv_mcast_forw_tt_node_get - get a multicast tt node
+ * @bat_priv: the bat priv with all the soft interface information
+ * @ethhdr: the ether header containing the multicast destination
+ *
+ * Returns an orig_node matching the multicast address provided by ethhdr
+ * via a translation table lookup. This increases the returned nodes refcount.
+ */
+static struct batadv_orig_node *
+batadv_mcast_forw_tt_node_get(struct batadv_priv *bat_priv,
+                             struct ethhdr *ethhdr)
+{
+       return batadv_transtable_search(bat_priv, ethhdr->h_source,
+                                       ethhdr->h_dest, BATADV_NO_FLAGS);
+}
+
+/**
+ * batadv_mcast_forw_mode - check on how to forward a multicast packet
+ * @bat_priv: the bat priv with all the soft interface information
+ * @skb: The multicast packet to check
+ * @orig: an originator to be set to forward the skb to
+ *
+ * Returns the forwarding mode as enum batadv_forw_mode and in case of
+ * BATADV_FORW_SINGLE set the orig to the single originator the skb
+ * should be forwarded to.
+ */
+enum batadv_forw_mode
+batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb,
+                      struct batadv_orig_node **orig)
+{
+       struct ethhdr *ethhdr;
+       int ret, tt_count;
+
+       ret = batadv_mcast_forw_mode_check(bat_priv, skb);
+       if (ret == -ENOMEM)
+               return BATADV_FORW_NONE;
+       else if (ret < 0)
+               return BATADV_FORW_ALL;
+
+       ethhdr = eth_hdr(skb);
+
+       tt_count = batadv_tt_global_hash_count(bat_priv, ethhdr->h_dest,
+                                              BATADV_NO_FLAGS);
+
+       switch (tt_count) {
+       case 1:
+               *orig = batadv_mcast_forw_tt_node_get(bat_priv, ethhdr);
+               if (*orig)
+                       return BATADV_FORW_SINGLE;
+
+               /* fall through */
+       case 0:
+               return BATADV_FORW_NONE;
+       default:
+               return BATADV_FORW_ALL;
+       }
+}
+
 /**
  * batadv_mcast_tvlv_ogm_handler_v1 - process incoming multicast tvlv container
  * @bat_priv: the bat priv with all the soft interface information
 
 #ifndef _NET_BATMAN_ADV_MULTICAST_H_
 #define _NET_BATMAN_ADV_MULTICAST_H_
 
+/**
+ * batadv_forw_mode - the way a packet should be forwarded as
+ * @BATADV_FORW_ALL: forward the packet to all nodes (currently via classic
+ *  flooding)
+ * @BATADV_FORW_SINGLE: forward the packet to a single node (currently via the
+ *  BATMAN unicast routing protocol)
+ * @BATADV_FORW_NONE: don't forward, drop it
+ */
+enum batadv_forw_mode {
+       BATADV_FORW_ALL,
+       BATADV_FORW_SINGLE,
+       BATADV_FORW_NONE,
+};
+
 #ifdef CONFIG_BATMAN_ADV_MCAST
 
 void batadv_mcast_mla_update(struct batadv_priv *bat_priv);
 
+enum batadv_forw_mode
+batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb,
+                      struct batadv_orig_node **mcast_single_orig);
+
 void batadv_mcast_init(struct batadv_priv *bat_priv);
 
 void batadv_mcast_free(struct batadv_priv *bat_priv);
        return;
 }
 
+static inline enum batadv_forw_mode
+batadv_mcast_forw_mode(struct batadv_priv *bat_priv, struct sk_buff *skb,
+                      struct batadv_orig_node **mcast_single_orig)
+{
+       return BATADV_FORW_ALL;
+}
+
 static inline int batadv_mcast_init(struct batadv_priv *bat_priv)
 {
        return 0;
 
  *
  * Returns NET_XMIT_DROP in case of error or NET_XMIT_SUCCESS otherwise.
  */
-static int batadv_send_skb_unicast(struct batadv_priv *bat_priv,
-                                  struct sk_buff *skb, int packet_type,
-                                  int packet_subtype,
-                                  struct batadv_orig_node *orig_node,
-                                  unsigned short vid)
+int batadv_send_skb_unicast(struct batadv_priv *bat_priv,
+                           struct sk_buff *skb, int packet_type,
+                           int packet_subtype,
+                           struct batadv_orig_node *orig_node,
+                           unsigned short vid)
 {
        struct ethhdr *ethhdr;
        struct batadv_unicast_packet *unicast_packet;
 
                                           struct sk_buff *skb,
                                           struct batadv_orig_node *orig_node,
                                           int packet_subtype);
+int batadv_send_skb_unicast(struct batadv_priv *bat_priv,
+                           struct sk_buff *skb, int packet_type,
+                           int packet_subtype,
+                           struct batadv_orig_node *orig_node,
+                           unsigned short vid);
 int batadv_send_skb_via_tt_generic(struct batadv_priv *bat_priv,
                                   struct sk_buff *skb, int packet_type,
                                   int packet_subtype, uint8_t *dst_hint,
 
 #include <linux/ethtool.h>
 #include <linux/etherdevice.h>
 #include <linux/if_vlan.h>
+#include "multicast.h"
 #include "bridge_loop_avoidance.h"
 #include "network-coding.h"
 
        unsigned short vid;
        uint32_t seqno;
        int gw_mode;
+       enum batadv_forw_mode forw_mode;
+       struct batadv_orig_node *mcast_single_orig = NULL;
 
        if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE)
                goto dropped;
                         * directed to a DHCP server
                         */
                        goto dropped;
-       }
 
 send:
+               if (do_bcast && !is_broadcast_ether_addr(ethhdr->h_dest)) {
+                       forw_mode = batadv_mcast_forw_mode(bat_priv, skb,
+                                                          &mcast_single_orig);
+                       if (forw_mode == BATADV_FORW_NONE)
+                               goto dropped;
+
+                       if (forw_mode == BATADV_FORW_SINGLE)
+                               do_bcast = false;
+               }
+       }
+
        batadv_skb_set_priority(skb, 0);
 
        /* ethernet packet should be broadcasted */
                        if (ret)
                                goto dropped;
                        ret = batadv_send_skb_via_gw(bat_priv, skb, vid);
+               } else if (mcast_single_orig) {
+                       ret = batadv_send_skb_unicast(bat_priv, skb,
+                                                     BATADV_UNICAST, 0,
+                                                     mcast_single_orig, vid);
                } else {
                        if (batadv_dat_snoop_outgoing_arp_request(bat_priv,
                                                                  skb))
 #endif
 #ifdef CONFIG_BATMAN_ADV_MCAST
        bat_priv->mcast.flags = BATADV_NO_FLAGS;
+       atomic_set(&bat_priv->multicast_mode, 1);
        atomic_set(&bat_priv->mcast.num_disabled, 0);
 #endif
        atomic_set(&bat_priv->gw_mode, BATADV_GW_MODE_OFF);
 
                     batadv_post_gw_reselect);
 static BATADV_ATTR(gw_bandwidth, S_IRUGO | S_IWUSR, batadv_show_gw_bwidth,
                   batadv_store_gw_bwidth);
+#ifdef CONFIG_BATMAN_ADV_MCAST
+BATADV_ATTR_SIF_BOOL(multicast_mode, S_IRUGO | S_IWUSR, NULL);
+#endif
 #ifdef CONFIG_BATMAN_ADV_DEBUG
 BATADV_ATTR_SIF_UINT(log_level, S_IRUGO | S_IWUSR, 0, BATADV_DBG_ALL, NULL);
 #endif
 #endif
 #ifdef CONFIG_BATMAN_ADV_DAT
        &batadv_attr_distributed_arp_table,
+#endif
+#ifdef CONFIG_BATMAN_ADV_MCAST
+       &batadv_attr_multicast_mode,
 #endif
        &batadv_attr_fragmentation,
        &batadv_attr_routing_algo,
 
        }
 }
 
+/**
+ * batadv_tt_global_hash_count - count the number of orig entries
+ * @hash: hash table containing the tt entries
+ * @addr: the mac address of the client to count entries for
+ * @vid: VLAN identifier
+ *
+ * Return the number of originators advertising the given address/data
+ * (excluding ourself).
+ */
+int batadv_tt_global_hash_count(struct batadv_priv *bat_priv,
+                               const uint8_t *addr, unsigned short vid)
+{
+       struct batadv_tt_global_entry *tt_global_entry;
+       int count;
+
+       tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid);
+       if (!tt_global_entry)
+               return 0;
+
+       count = atomic_read(&tt_global_entry->orig_list_count);
+       batadv_tt_global_entry_free_ref(tt_global_entry);
+
+       return count;
+}
+
 static void batadv_tt_orig_list_entry_free_rcu(struct rcu_head *rcu)
 {
        struct batadv_tt_orig_list_entry *orig_entry;
        hlist_add_head_rcu(&orig_entry->list,
                           &tt_global->orig_list);
        spin_unlock_bh(&tt_global->list_lock);
+       atomic_inc(&tt_global->orig_list_count);
+
 out:
        if (orig_entry)
                batadv_tt_orig_list_entry_free_ref(orig_entry);
                common->added_at = jiffies;
 
                INIT_HLIST_HEAD(&tt_global_entry->orig_list);
+               atomic_set(&tt_global_entry->orig_list_count, 0);
                spin_lock_init(&tt_global_entry->list_lock);
 
                hash_added = batadv_hash_add(bat_priv->tt.global_hash,
        return 0;
 }
 
+/**
+ * batadv_tt_global_del_orig_entry - remove and free an orig_entry
+ * @tt_global_entry: the global entry to remove the orig_entry from
+ * @orig_entry: the orig entry to remove and free
+ *
+ * Remove an orig_entry from its list in the given tt_global_entry and
+ * free this orig_entry afterwards.
+ */
+static void
+batadv_tt_global_del_orig_entry(struct batadv_tt_global_entry *tt_global_entry,
+                               struct batadv_tt_orig_list_entry *orig_entry)
+{
+       batadv_tt_global_size_dec(orig_entry->orig_node,
+                                 tt_global_entry->common.vid);
+       atomic_dec(&tt_global_entry->orig_list_count);
+       hlist_del_rcu(&orig_entry->list);
+       batadv_tt_orig_list_entry_free_ref(orig_entry);
+}
+
 /* deletes the orig list of a tt_global_entry */
 static void
 batadv_tt_global_del_orig_list(struct batadv_tt_global_entry *tt_global_entry)
 
        spin_lock_bh(&tt_global_entry->list_lock);
        head = &tt_global_entry->orig_list;
-       hlist_for_each_entry_safe(orig_entry, safe, head, list) {
-               hlist_del_rcu(&orig_entry->list);
-               batadv_tt_global_size_dec(orig_entry->orig_node,
-                                         tt_global_entry->common.vid);
-               batadv_tt_orig_list_entry_free_ref(orig_entry);
-       }
+       hlist_for_each_entry_safe(orig_entry, safe, head, list)
+               batadv_tt_global_del_orig_entry(tt_global_entry, orig_entry);
        spin_unlock_bh(&tt_global_entry->list_lock);
 }
 
+/**
+ * batadv_tt_global_del_orig_node - remove orig_node from a global tt entry
+ * @bat_priv: the bat priv with all the soft interface information
+ * @tt_global_entry: the global entry to remove the orig_node from
+ * @orig_node: the originator announcing the client
+ * @message: message to append to the log on deletion
+ *
+ * Remove the given orig_node and its according orig_entry from the given
+ * global tt entry.
+ */
 static void
-batadv_tt_global_del_orig_entry(struct batadv_priv *bat_priv,
-                               struct batadv_tt_global_entry *tt_global_entry,
-                               struct batadv_orig_node *orig_node,
-                               const char *message)
+batadv_tt_global_del_orig_node(struct batadv_priv *bat_priv,
+                              struct batadv_tt_global_entry *tt_global_entry,
+                              struct batadv_orig_node *orig_node,
+                              const char *message)
 {
        struct hlist_head *head;
        struct hlist_node *safe;
                                   orig_node->orig,
                                   tt_global_entry->common.addr,
                                   BATADV_PRINT_VID(vid), message);
-                       hlist_del_rcu(&orig_entry->list);
-                       batadv_tt_global_size_dec(orig_node,
-                                                 tt_global_entry->common.vid);
-                       batadv_tt_orig_list_entry_free_ref(orig_entry);
+                       batadv_tt_global_del_orig_entry(tt_global_entry,
+                                                       orig_entry);
                }
        }
        spin_unlock_bh(&tt_global_entry->list_lock);
                /* there is another entry, we can simply delete this
                 * one and can still use the other one.
                 */
-               batadv_tt_global_del_orig_entry(bat_priv, tt_global_entry,
-                                               orig_node, message);
+               batadv_tt_global_del_orig_node(bat_priv, tt_global_entry,
+                                              orig_node, message);
 }
 
 /**
                goto out;
 
        if (!roaming) {
-               batadv_tt_global_del_orig_entry(bat_priv, tt_global_entry,
-                                               orig_node, message);
+               batadv_tt_global_del_orig_node(bat_priv, tt_global_entry,
+                                              orig_node, message);
 
                if (hlist_empty(&tt_global_entry->orig_list))
                        batadv_tt_global_free(bat_priv, tt_global_entry,
                                                 struct batadv_tt_global_entry,
                                                 common);
 
-                       batadv_tt_global_del_orig_entry(bat_priv, tt_global,
-                                                       orig_node, message);
+                       batadv_tt_global_del_orig_node(bat_priv, tt_global,
+                                                      orig_node, message);
 
                        if (hlist_empty(&tt_global->orig_list)) {
                                vid = tt_global->common.vid;
 
 void batadv_tt_global_del_orig(struct batadv_priv *bat_priv,
                               struct batadv_orig_node *orig_node,
                               int32_t match_vid, const char *message);
+int batadv_tt_global_hash_count(struct batadv_priv *bat_priv,
+                               const uint8_t *addr, unsigned short vid);
 struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv,
                                                  const uint8_t *src,
                                                  const uint8_t *addr,
 
  *  enabled
  * @distributed_arp_table: bool indicating whether distributed ARP table is
  *  enabled
+ * @multicast_mode: Enable or disable multicast optimizations on this node's
+ *  sender/originating side
  * @gw_mode: gateway operation: off, client or server (see batadv_gw_modes)
  * @gw_sel_class: gateway selection class (applies if gw_mode client)
  * @orig_interval: OGM broadcast interval in milliseconds
 #endif
 #ifdef CONFIG_BATMAN_ADV_DAT
        atomic_t distributed_arp_table;
+#endif
+#ifdef CONFIG_BATMAN_ADV_MCAST
+       atomic_t multicast_mode;
 #endif
        atomic_t gw_mode;
        atomic_t gw_sel_class;
  * struct batadv_tt_global_entry - translation table global entry data
  * @common: general translation table data
  * @orig_list: list of orig nodes announcing this non-mesh client
+ * @orig_list_count: number of items in the orig_list
  * @list_lock: lock protecting orig_list
  * @roam_at: time at which TT_GLOBAL_ROAM was set
  */
 struct batadv_tt_global_entry {
        struct batadv_tt_common_entry common;
        struct hlist_head orig_list;
+       atomic_t orig_list_count;
        spinlock_t list_lock;   /* protects orig_list */
        unsigned long roam_at;
 };