OSDN Git Service

Merge tag 'batadv-next-for-davem-20161119' of git://git.open-mesh.org/linux-merge
authorDavid S. Miller <davem@davemloft.net>
Sat, 19 Nov 2016 16:13:05 +0000 (11:13 -0500)
committerDavid S. Miller <davem@davemloft.net>
Sat, 19 Nov 2016 16:13:05 +0000 (11:13 -0500)
Simon Wunderlich says:

====================
This feature patchset includes the following changes:

 - 6 patches adding functionality to detect a WiFi interface under
   other virtual interfaces, like VLANs. They introduce a cache for
   the detected the WiFi configuration to avoid RTNL locking in
   critical sections. Patches have been prepared by Marek Lindner
   and Sven Eckelmann

 - Enable automatic module loading for genl requests, by Sven Eckelmann

 - Fix a potential race condition on interface removal. This is not
   happening very often in practice, but requires bigger changes to fix,
   so we are sending this to net-next. By Linus Luessing
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
net/batman-adv/bat_iv_ogm.c
net/batman-adv/bat_v_elp.c
net/batman-adv/hard-interface.c
net/batman-adv/hard-interface.h
net/batman-adv/main.c
net/batman-adv/send.c
net/batman-adv/send.h
net/batman-adv/translation-table.c
net/batman-adv/types.h

index bd39247..f00f666 100644 (file)
@@ -717,17 +717,10 @@ static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff,
        if (direct_link)
                forw_packet_aggr->direct_link_flags |= 1;
 
-       /* add new packet to packet list */
-       spin_lock_bh(&bat_priv->forw_bat_list_lock);
-       hlist_add_head(&forw_packet_aggr->list, &bat_priv->forw_bat_list);
-       spin_unlock_bh(&bat_priv->forw_bat_list_lock);
-
-       /* start timer for this packet */
        INIT_DELAYED_WORK(&forw_packet_aggr->delayed_work,
                          batadv_iv_send_outstanding_bat_ogm_packet);
-       queue_delayed_work(batadv_event_workqueue,
-                          &forw_packet_aggr->delayed_work,
-                          send_time - jiffies);
+
+       batadv_forw_packet_ogmv1_queue(bat_priv, forw_packet_aggr, send_time);
 }
 
 /* aggregate a new packet into the existing ogm packet */
@@ -1272,7 +1265,7 @@ static bool batadv_iv_ogm_calc_tq(struct batadv_orig_node *orig_node,
         */
        tq_iface_penalty = BATADV_TQ_MAX_VALUE;
        if (if_outgoing && (if_incoming == if_outgoing) &&
-           batadv_is_wifi_netdev(if_outgoing->net_dev))
+           batadv_is_wifi_hardif(if_outgoing))
                tq_iface_penalty = batadv_hop_penalty(BATADV_TQ_MAX_VALUE,
                                                      bat_priv);
 
@@ -1789,9 +1782,6 @@ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work)
        forw_packet = container_of(delayed_work, struct batadv_forw_packet,
                                   delayed_work);
        bat_priv = netdev_priv(forw_packet->if_incoming->soft_iface);
-       spin_lock_bh(&bat_priv->forw_bat_list_lock);
-       hlist_del(&forw_packet->list);
-       spin_unlock_bh(&bat_priv->forw_bat_list_lock);
 
        if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING) {
                dropped = true;
@@ -1813,7 +1803,10 @@ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work)
                batadv_iv_ogm_schedule(forw_packet->if_incoming);
 
 out:
-       batadv_forw_packet_free(forw_packet, dropped);
+       /* do we get something for free()? */
+       if (batadv_forw_packet_steal(forw_packet,
+                                    &bat_priv->forw_bat_list_lock))
+               batadv_forw_packet_free(forw_packet, dropped);
 }
 
 static int batadv_iv_ogm_receive(struct sk_buff *skb,
index 54bdd41..f2fb2f0 100644 (file)
@@ -75,6 +75,7 @@ static u32 batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh)
 {
        struct batadv_hard_iface *hard_iface = neigh->if_incoming;
        struct ethtool_link_ksettings link_settings;
+       struct net_device *real_netdev;
        struct station_info sinfo;
        u32 throughput;
        int ret;
@@ -89,23 +90,27 @@ static u32 batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh)
        /* if this is a wireless device, then ask its throughput through
         * cfg80211 API
         */
-       if (batadv_is_wifi_netdev(hard_iface->net_dev)) {
-               if (hard_iface->net_dev->ieee80211_ptr) {
-                       ret = cfg80211_get_station(hard_iface->net_dev,
-                                                  neigh->addr, &sinfo);
-                       if (ret == -ENOENT) {
-                               /* Node is not associated anymore! It would be
-                                * possible to delete this neighbor. For now set
-                                * the throughput metric to 0.
-                                */
-                               return 0;
-                       }
-                       if (!ret)
-                               return sinfo.expected_throughput / 100;
+       if (batadv_is_wifi_hardif(hard_iface)) {
+               if (!batadv_is_cfg80211_hardif(hard_iface))
+                       /* unsupported WiFi driver version */
+                       goto default_throughput;
+
+               real_netdev = batadv_get_real_netdev(hard_iface->net_dev);
+               if (!real_netdev)
+                       goto default_throughput;
+
+               ret = cfg80211_get_station(real_netdev, neigh->addr, &sinfo);
+
+               dev_put(real_netdev);
+               if (ret == -ENOENT) {
+                       /* Node is not associated anymore! It would be
+                        * possible to delete this neighbor. For now set
+                        * the throughput metric to 0.
+                        */
+                       return 0;
                }
-
-               /* unsupported WiFi driver version */
-               goto default_throughput;
+               if (!ret)
+                       return sinfo.expected_throughput / 100;
        }
 
        /* if not a wifi interface, check if this device provides data via
@@ -187,7 +192,7 @@ batadv_v_elp_wifi_neigh_probe(struct batadv_hardif_neigh_node *neigh)
        int elp_skb_len;
 
        /* this probing routine is for Wifi neighbours only */
-       if (!batadv_is_wifi_netdev(hard_iface->net_dev))
+       if (!batadv_is_wifi_hardif(hard_iface))
                return true;
 
        /* probe the neighbor only if no unicast packets have been sent
@@ -352,7 +357,7 @@ int batadv_v_elp_iface_enable(struct batadv_hard_iface *hard_iface)
        /* warn the user (again) if there is no throughput data is available */
        hard_iface->bat_v.flags &= ~BATADV_WARNING_DEFAULT;
 
-       if (batadv_is_wifi_netdev(hard_iface->net_dev))
+       if (batadv_is_wifi_hardif(hard_iface))
                hard_iface->bat_v.flags &= ~BATADV_FULL_DUPLEX;
 
        INIT_DELAYED_WORK(&hard_iface->bat_v.elp_wq,
index dc1816e..672150b 100644 (file)
@@ -92,8 +92,8 @@ out:
  *
  * Return: result of rtnl_link_ops->get_link_net or @fallback_net
  */
-static const struct net *batadv_getlink_net(const struct net_device *netdev,
-                                           const struct net *fallback_net)
+static struct net *batadv_getlink_net(const struct net_device *netdev,
+                                     struct net *fallback_net)
 {
        if (!netdev->rtnl_link_ops)
                return fallback_net;
@@ -116,9 +116,9 @@ static const struct net *batadv_getlink_net(const struct net_device *netdev,
  * Return: true if the devices are each others parent, otherwise false
  */
 static bool batadv_mutual_parents(const struct net_device *dev1,
-                                 const struct net *net1,
+                                 struct net *net1,
                                  const struct net_device *dev2,
-                                 const struct net *net2)
+                                 struct net *net2)
 {
        int dev1_parent_iflink = dev_get_iflink(dev1);
        int dev2_parent_iflink = dev_get_iflink(dev2);
@@ -154,7 +154,7 @@ static bool batadv_is_on_batman_iface(const struct net_device *net_dev)
 {
        struct net *net = dev_net(net_dev);
        struct net_device *parent_dev;
-       const struct net *parent_net;
+       struct net *parent_net;
        bool ret;
 
        /* check if this is a batman-adv mesh interface */
@@ -202,13 +202,77 @@ static bool batadv_is_valid_iface(const struct net_device *net_dev)
 }
 
 /**
- * batadv_is_wifi_netdev - check if the given net_device struct is a wifi
- *  interface
+ * batadv_get_real_netdevice - check if the given netdev struct is a virtual
+ *  interface on top of another 'real' interface
+ * @netdev: the device to check
+ *
+ * Callers must hold the rtnl semaphore. You may want batadv_get_real_netdev()
+ * instead of this.
+ *
+ * Return: the 'real' net device or the original net device and NULL in case
+ *  of an error.
+ */
+static struct net_device *batadv_get_real_netdevice(struct net_device *netdev)
+{
+       struct batadv_hard_iface *hard_iface = NULL;
+       struct net_device *real_netdev = NULL;
+       struct net *real_net;
+       struct net *net;
+       int ifindex;
+
+       ASSERT_RTNL();
+
+       if (!netdev)
+               return NULL;
+
+       if (netdev->ifindex == dev_get_iflink(netdev)) {
+               dev_hold(netdev);
+               return netdev;
+       }
+
+       hard_iface = batadv_hardif_get_by_netdev(netdev);
+       if (!hard_iface || !hard_iface->soft_iface)
+               goto out;
+
+       net = dev_net(hard_iface->soft_iface);
+       ifindex = dev_get_iflink(netdev);
+       real_net = batadv_getlink_net(netdev, net);
+       real_netdev = dev_get_by_index(real_net, ifindex);
+
+out:
+       if (hard_iface)
+               batadv_hardif_put(hard_iface);
+       return real_netdev;
+}
+
+/**
+ * batadv_get_real_netdev - check if the given net_device struct is a virtual
+ *  interface on top of another 'real' interface
  * @net_device: the device to check
  *
- * Return: true if the net device is a 802.11 wireless device, false otherwise.
+ * Return: the 'real' net device or the original net device and NULL in case
+ *  of an error.
  */
-bool batadv_is_wifi_netdev(struct net_device *net_device)
+struct net_device *batadv_get_real_netdev(struct net_device *net_device)
+{
+       struct net_device *real_netdev;
+
+       rtnl_lock();
+       real_netdev = batadv_get_real_netdevice(net_device);
+       rtnl_unlock();
+
+       return real_netdev;
+}
+
+/**
+ * batadv_is_wext_netdev - check if the given net_device struct is a
+ *  wext wifi interface
+ * @net_device: the device to check
+ *
+ * Return: true if the net device is a wext wireless device, false
+ *  otherwise.
+ */
+static bool batadv_is_wext_netdev(struct net_device *net_device)
 {
        if (!net_device)
                return false;
@@ -221,6 +285,22 @@ bool batadv_is_wifi_netdev(struct net_device *net_device)
                return true;
 #endif
 
+       return false;
+}
+
+/**
+ * batadv_is_cfg80211_netdev - check if the given net_device struct is a
+ *  cfg80211 wifi interface
+ * @net_device: the device to check
+ *
+ * Return: true if the net device is a cfg80211 wireless device, false
+ *  otherwise.
+ */
+static bool batadv_is_cfg80211_netdev(struct net_device *net_device)
+{
+       if (!net_device)
+               return false;
+
        /* cfg80211 drivers have to set ieee80211_ptr */
        if (net_device->ieee80211_ptr)
                return true;
@@ -229,6 +309,73 @@ bool batadv_is_wifi_netdev(struct net_device *net_device)
 }
 
 /**
+ * batadv_wifi_flags_evaluate - calculate wifi flags for net_device
+ * @net_device: the device to check
+ *
+ * Return: batadv_hard_iface_wifi_flags flags of the device
+ */
+static u32 batadv_wifi_flags_evaluate(struct net_device *net_device)
+{
+       u32 wifi_flags = 0;
+       struct net_device *real_netdev;
+
+       if (batadv_is_wext_netdev(net_device))
+               wifi_flags |= BATADV_HARDIF_WIFI_WEXT_DIRECT;
+
+       if (batadv_is_cfg80211_netdev(net_device))
+               wifi_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
+
+       real_netdev = batadv_get_real_netdevice(net_device);
+       if (!real_netdev)
+               return wifi_flags;
+
+       if (real_netdev == net_device)
+               goto out;
+
+       if (batadv_is_wext_netdev(real_netdev))
+               wifi_flags |= BATADV_HARDIF_WIFI_WEXT_INDIRECT;
+
+       if (batadv_is_cfg80211_netdev(real_netdev))
+               wifi_flags |= BATADV_HARDIF_WIFI_CFG80211_INDIRECT;
+
+out:
+       dev_put(real_netdev);
+       return wifi_flags;
+}
+
+/**
+ * batadv_is_cfg80211_hardif - check if the given hardif is a cfg80211 wifi
+ *  interface
+ * @hard_iface: the device to check
+ *
+ * Return: true if the net device is a cfg80211 wireless device, false
+ *  otherwise.
+ */
+bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface)
+{
+       u32 allowed_flags = 0;
+
+       allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
+       allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_INDIRECT;
+
+       return !!(hard_iface->wifi_flags & allowed_flags);
+}
+
+/**
+ * batadv_is_wifi_hardif - check if the given hardif is a wifi interface
+ * @hard_iface: the device to check
+ *
+ * Return: true if the net device is a 802.11 wireless device, false otherwise.
+ */
+bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface)
+{
+       if (!hard_iface)
+               return false;
+
+       return hard_iface->wifi_flags != 0;
+}
+
+/**
  * batadv_hardif_no_broadcast - check whether (re)broadcast is necessary
  * @if_outgoing: the outgoing interface checked and considered for (re)broadcast
  * @orig_addr: the originator of this packet
@@ -748,7 +895,8 @@ batadv_hardif_add_interface(struct net_device *net_dev)
        kref_init(&hard_iface->refcount);
 
        hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT;
-       if (batadv_is_wifi_netdev(net_dev))
+       hard_iface->wifi_flags = batadv_wifi_flags_evaluate(net_dev);
+       if (batadv_is_wifi_hardif(hard_iface))
                hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
 
        batadv_v_hardif_init(hard_iface);
@@ -857,6 +1005,11 @@ static int batadv_hard_if_event(struct notifier_block *this,
                if (hard_iface == primary_if)
                        batadv_primary_if_update_addr(bat_priv, NULL);
                break;
+       case NETDEV_CHANGEUPPER:
+               hard_iface->wifi_flags = batadv_wifi_flags_evaluate(net_dev);
+               if (batadv_is_wifi_hardif(hard_iface))
+                       hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
+               break;
        default:
                break;
        }
index a043182..d6309a4 100644 (file)
@@ -65,8 +65,9 @@ enum batadv_hard_if_cleanup {
 
 extern struct notifier_block batadv_hard_if_notifier;
 
-bool batadv_is_wifi_netdev(struct net_device *net_device);
-bool batadv_is_wifi_iface(int ifindex);
+struct net_device *batadv_get_real_netdev(struct net_device *net_device);
+bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface);
+bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface);
 struct batadv_hard_iface*
 batadv_hardif_get_by_netdev(const struct net_device *net_dev);
 int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface,
index 6b5dae6..d46415e 100644 (file)
@@ -23,6 +23,7 @@
 #include <linux/crc32c.h>
 #include <linux/errno.h>
 #include <linux/fs.h>
+#include <linux/genetlink.h>
 #include <linux/if_ether.h>
 #include <linux/if_vlan.h>
 #include <linux/init.h>
@@ -44,6 +45,7 @@
 #include <linux/workqueue.h>
 #include <net/dsfield.h>
 #include <net/rtnetlink.h>
+#include <uapi/linux/batman_adv.h>
 
 #include "bat_algo.h"
 #include "bat_iv_ogm.h"
@@ -648,3 +650,4 @@ MODULE_DESCRIPTION(BATADV_DRIVER_DESC);
 MODULE_SUPPORTED_DEVICE(BATADV_DRIVER_DEVICE);
 MODULE_VERSION(BATADV_SOURCE_VERSION);
 MODULE_ALIAS_RTNL_LINK("batadv");
+MODULE_ALIAS_GENL_FAMILY(BATADV_NL_NAME);
index 9ea272e..49021b7 100644 (file)
@@ -19,6 +19,7 @@
 #include "main.h"
 
 #include <linux/atomic.h>
+#include <linux/bug.h>
 #include <linux/byteorder/generic.h>
 #include <linux/errno.h>
 #include <linux/etherdevice.h>
@@ -520,6 +521,8 @@ batadv_forw_packet_alloc(struct batadv_hard_iface *if_incoming,
        if (if_outgoing)
                kref_get(&if_outgoing->refcount);
 
+       INIT_HLIST_NODE(&forw_packet->list);
+       INIT_HLIST_NODE(&forw_packet->cleanup_list);
        forw_packet->skb = NULL;
        forw_packet->queue_left = queue_left;
        forw_packet->if_incoming = if_incoming;
@@ -535,19 +538,191 @@ err:
        return NULL;
 }
 
+/**
+ * batadv_forw_packet_was_stolen - check whether someone stole this packet
+ * @forw_packet: the forwarding packet to check
+ *
+ * This function checks whether the given forwarding packet was claimed by
+ * someone else for free().
+ *
+ * Return: True if someone stole it, false otherwise.
+ */
+static bool
+batadv_forw_packet_was_stolen(struct batadv_forw_packet *forw_packet)
+{
+       return !hlist_unhashed(&forw_packet->cleanup_list);
+}
+
+/**
+ * batadv_forw_packet_steal - claim a forw_packet for free()
+ * @forw_packet: the forwarding packet to steal
+ * @lock: a key to the store to steal from (e.g. forw_{bat,bcast}_list_lock)
+ *
+ * This function tries to steal a specific forw_packet from global
+ * visibility for the purpose of getting it for free(). That means
+ * the caller is *not* allowed to requeue it afterwards.
+ *
+ * Return: True if stealing was successful. False if someone else stole it
+ * before us.
+ */
+bool batadv_forw_packet_steal(struct batadv_forw_packet *forw_packet,
+                             spinlock_t *lock)
+{
+       /* did purging routine steal it earlier? */
+       spin_lock_bh(lock);
+       if (batadv_forw_packet_was_stolen(forw_packet)) {
+               spin_unlock_bh(lock);
+               return false;
+       }
+
+       hlist_del_init(&forw_packet->list);
+
+       /* Just to spot misuse of this function */
+       hlist_add_fake(&forw_packet->cleanup_list);
+
+       spin_unlock_bh(lock);
+       return true;
+}
+
+/**
+ * batadv_forw_packet_list_steal - claim a list of forward packets for free()
+ * @forw_list: the to be stolen forward packets
+ * @cleanup_list: a backup pointer, to be able to dispose the packet later
+ * @hard_iface: the interface to steal forward packets from
+ *
+ * This function claims responsibility to free any forw_packet queued on the
+ * given hard_iface. If hard_iface is NULL forwarding packets on all hard
+ * interfaces will be claimed.
+ *
+ * The packets are being moved from the forw_list to the cleanup_list and
+ * by that allows already running threads to notice the claiming.
+ */
 static void
-_batadv_add_bcast_packet_to_list(struct batadv_priv *bat_priv,
-                                struct batadv_forw_packet *forw_packet,
-                                unsigned long send_time)
+batadv_forw_packet_list_steal(struct hlist_head *forw_list,
+                             struct hlist_head *cleanup_list,
+                             const struct batadv_hard_iface *hard_iface)
 {
-       /* add new packet to packet list */
-       spin_lock_bh(&bat_priv->forw_bcast_list_lock);
-       hlist_add_head(&forw_packet->list, &bat_priv->forw_bcast_list);
-       spin_unlock_bh(&bat_priv->forw_bcast_list_lock);
+       struct batadv_forw_packet *forw_packet;
+       struct hlist_node *safe_tmp_node;
+
+       hlist_for_each_entry_safe(forw_packet, safe_tmp_node,
+                                 forw_list, list) {
+               /* if purge_outstanding_packets() was called with an argument
+                * we delete only packets belonging to the given interface
+                */
+               if (hard_iface &&
+                   (forw_packet->if_incoming != hard_iface) &&
+                   (forw_packet->if_outgoing != hard_iface))
+                       continue;
+
+               hlist_del(&forw_packet->list);
+               hlist_add_head(&forw_packet->cleanup_list, cleanup_list);
+       }
+}
+
+/**
+ * batadv_forw_packet_list_free - free a list of forward packets
+ * @head: a list of to be freed forw_packets
+ *
+ * This function cancels the scheduling of any packet in the provided list,
+ * waits for any possibly running packet forwarding thread to finish and
+ * finally, safely frees this forward packet.
+ *
+ * This function might sleep.
+ */
+static void batadv_forw_packet_list_free(struct hlist_head *head)
+{
+       struct batadv_forw_packet *forw_packet;
+       struct hlist_node *safe_tmp_node;
+
+       hlist_for_each_entry_safe(forw_packet, safe_tmp_node, head,
+                                 cleanup_list) {
+               cancel_delayed_work_sync(&forw_packet->delayed_work);
 
-       /* start timer for this packet */
-       queue_delayed_work(batadv_event_workqueue, &forw_packet->delayed_work,
-                          send_time);
+               hlist_del(&forw_packet->cleanup_list);
+               batadv_forw_packet_free(forw_packet, true);
+       }
+}
+
+/**
+ * batadv_forw_packet_queue - try to queue a forwarding packet
+ * @forw_packet: the forwarding packet to queue
+ * @lock: a key to the store (e.g. forw_{bat,bcast}_list_lock)
+ * @head: the shelve to queue it on (e.g. forw_{bat,bcast}_list)
+ * @send_time: timestamp (jiffies) when the packet is to be sent
+ *
+ * This function tries to (re)queue a forwarding packet. Requeuing
+ * is prevented if the according interface is shutting down
+ * (e.g. if batadv_forw_packet_list_steal() was called for this
+ * packet earlier).
+ *
+ * Calling batadv_forw_packet_queue() after a call to
+ * batadv_forw_packet_steal() is forbidden!
+ *
+ * Caller needs to ensure that forw_packet->delayed_work was initialized.
+ */
+static void batadv_forw_packet_queue(struct batadv_forw_packet *forw_packet,
+                                    spinlock_t *lock, struct hlist_head *head,
+                                    unsigned long send_time)
+{
+       spin_lock_bh(lock);
+
+       /* did purging routine steal it from us? */
+       if (batadv_forw_packet_was_stolen(forw_packet)) {
+               /* If you got it for free() without trouble, then
+                * don't get back into the queue after stealing...
+                */
+               WARN_ONCE(hlist_fake(&forw_packet->cleanup_list),
+                         "Requeuing after batadv_forw_packet_steal() not allowed!\n");
+
+               spin_unlock_bh(lock);
+               return;
+       }
+
+       hlist_del_init(&forw_packet->list);
+       hlist_add_head(&forw_packet->list, head);
+
+       queue_delayed_work(batadv_event_workqueue,
+                          &forw_packet->delayed_work,
+                          send_time - jiffies);
+       spin_unlock_bh(lock);
+}
+
+/**
+ * batadv_forw_packet_bcast_queue - try to queue a broadcast packet
+ * @bat_priv: the bat priv with all the soft interface information
+ * @forw_packet: the forwarding packet to queue
+ * @send_time: timestamp (jiffies) when the packet is to be sent
+ *
+ * This function tries to (re)queue a broadcast packet.
+ *
+ * Caller needs to ensure that forw_packet->delayed_work was initialized.
+ */
+static void
+batadv_forw_packet_bcast_queue(struct batadv_priv *bat_priv,
+                              struct batadv_forw_packet *forw_packet,
+                              unsigned long send_time)
+{
+       batadv_forw_packet_queue(forw_packet, &bat_priv->forw_bcast_list_lock,
+                                &bat_priv->forw_bcast_list, send_time);
+}
+
+/**
+ * batadv_forw_packet_ogmv1_queue - try to queue an OGMv1 packet
+ * @bat_priv: the bat priv with all the soft interface information
+ * @forw_packet: the forwarding packet to queue
+ * @send_time: timestamp (jiffies) when the packet is to be sent
+ *
+ * This function tries to (re)queue an OGMv1 packet.
+ *
+ * Caller needs to ensure that forw_packet->delayed_work was initialized.
+ */
+void batadv_forw_packet_ogmv1_queue(struct batadv_priv *bat_priv,
+                                   struct batadv_forw_packet *forw_packet,
+                                   unsigned long send_time)
+{
+       batadv_forw_packet_queue(forw_packet, &bat_priv->forw_bat_list_lock,
+                                &bat_priv->forw_bat_list, send_time);
 }
 
 /**
@@ -600,7 +775,7 @@ int batadv_add_bcast_packet_to_list(struct batadv_priv *bat_priv,
        INIT_DELAYED_WORK(&forw_packet->delayed_work,
                          batadv_send_outstanding_bcast_packet);
 
-       _batadv_add_bcast_packet_to_list(bat_priv, forw_packet, delay);
+       batadv_forw_packet_bcast_queue(bat_priv, forw_packet, jiffies + delay);
        return NETDEV_TX_OK;
 
 err_packet_free:
@@ -619,6 +794,7 @@ static void batadv_send_outstanding_bcast_packet(struct work_struct *work)
        struct sk_buff *skb1;
        struct net_device *soft_iface;
        struct batadv_priv *bat_priv;
+       unsigned long send_time = jiffies + msecs_to_jiffies(5);
        bool dropped = false;
        u8 *neigh_addr;
        u8 *orig_neigh;
@@ -630,10 +806,6 @@ static void batadv_send_outstanding_bcast_packet(struct work_struct *work)
        soft_iface = forw_packet->if_incoming->soft_iface;
        bat_priv = netdev_priv(soft_iface);
 
-       spin_lock_bh(&bat_priv->forw_bcast_list_lock);
-       hlist_del(&forw_packet->list);
-       spin_unlock_bh(&bat_priv->forw_bcast_list_lock);
-
        if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING) {
                dropped = true;
                goto out;
@@ -714,22 +886,34 @@ static void batadv_send_outstanding_bcast_packet(struct work_struct *work)
 
        /* if we still have some more bcasts to send */
        if (forw_packet->num_packets < BATADV_NUM_BCASTS_MAX) {
-               _batadv_add_bcast_packet_to_list(bat_priv, forw_packet,
-                                                msecs_to_jiffies(5));
+               batadv_forw_packet_bcast_queue(bat_priv, forw_packet,
+                                              send_time);
                return;
        }
 
 out:
-       batadv_forw_packet_free(forw_packet, dropped);
+       /* do we get something for free()? */
+       if (batadv_forw_packet_steal(forw_packet,
+                                    &bat_priv->forw_bcast_list_lock))
+               batadv_forw_packet_free(forw_packet, dropped);
 }
 
+/**
+ * batadv_purge_outstanding_packets - stop/purge scheduled bcast/OGMv1 packets
+ * @bat_priv: the bat priv with all the soft interface information
+ * @hard_iface: the hard interface to cancel and purge bcast/ogm packets on
+ *
+ * This method cancels and purges any broadcast and OGMv1 packet on the given
+ * hard_iface. If hard_iface is NULL, broadcast and OGMv1 packets on all hard
+ * interfaces will be canceled and purged.
+ *
+ * This function might sleep.
+ */
 void
 batadv_purge_outstanding_packets(struct batadv_priv *bat_priv,
                                 const struct batadv_hard_iface *hard_iface)
 {
-       struct batadv_forw_packet *forw_packet;
-       struct hlist_node *safe_tmp_node;
-       bool pending;
+       struct hlist_head head = HLIST_HEAD_INIT;
 
        if (hard_iface)
                batadv_dbg(BATADV_DBG_BATMAN, bat_priv,
@@ -739,57 +923,18 @@ batadv_purge_outstanding_packets(struct batadv_priv *bat_priv,
                batadv_dbg(BATADV_DBG_BATMAN, bat_priv,
                           "purge_outstanding_packets()\n");
 
-       /* free bcast list */
+       /* claim bcast list for free() */
        spin_lock_bh(&bat_priv->forw_bcast_list_lock);
-       hlist_for_each_entry_safe(forw_packet, safe_tmp_node,
-                                 &bat_priv->forw_bcast_list, list) {
-               /* if purge_outstanding_packets() was called with an argument
-                * we delete only packets belonging to the given interface
-                */
-               if ((hard_iface) &&
-                   (forw_packet->if_incoming != hard_iface) &&
-                   (forw_packet->if_outgoing != hard_iface))
-                       continue;
-
-               spin_unlock_bh(&bat_priv->forw_bcast_list_lock);
-
-               /* batadv_send_outstanding_bcast_packet() will lock the list to
-                * delete the item from the list
-                */
-               pending = cancel_delayed_work_sync(&forw_packet->delayed_work);
-               spin_lock_bh(&bat_priv->forw_bcast_list_lock);
-
-               if (pending) {
-                       hlist_del(&forw_packet->list);
-                       batadv_forw_packet_free(forw_packet, true);
-               }
-       }
+       batadv_forw_packet_list_steal(&bat_priv->forw_bcast_list, &head,
+                                     hard_iface);
        spin_unlock_bh(&bat_priv->forw_bcast_list_lock);
 
-       /* free batman packet list */
+       /* claim batman packet list for free() */
        spin_lock_bh(&bat_priv->forw_bat_list_lock);
-       hlist_for_each_entry_safe(forw_packet, safe_tmp_node,
-                                 &bat_priv->forw_bat_list, list) {
-               /* if purge_outstanding_packets() was called with an argument
-                * we delete only packets belonging to the given interface
-                */
-               if ((hard_iface) &&
-                   (forw_packet->if_incoming != hard_iface) &&
-                   (forw_packet->if_outgoing != hard_iface))
-                       continue;
-
-               spin_unlock_bh(&bat_priv->forw_bat_list_lock);
-
-               /* send_outstanding_bat_packet() will lock the list to
-                * delete the item from the list
-                */
-               pending = cancel_delayed_work_sync(&forw_packet->delayed_work);
-               spin_lock_bh(&bat_priv->forw_bat_list_lock);
-
-               if (pending) {
-                       hlist_del(&forw_packet->list);
-                       batadv_forw_packet_free(forw_packet, true);
-               }
-       }
+       batadv_forw_packet_list_steal(&bat_priv->forw_bat_list, &head,
+                                     hard_iface);
        spin_unlock_bh(&bat_priv->forw_bat_list_lock);
+
+       /* then cancel or wait for packet workers to finish and free */
+       batadv_forw_packet_list_free(&head);
 }
index c580194..a94e1e8 100644 (file)
@@ -21,6 +21,7 @@
 #include "main.h"
 
 #include <linux/compiler.h>
+#include <linux/spinlock.h>
 #include <linux/types.h>
 
 #include "packet.h"
@@ -34,6 +35,10 @@ batadv_forw_packet_alloc(struct batadv_hard_iface *if_incoming,
                         struct batadv_hard_iface *if_outgoing,
                         atomic_t *queue_left,
                         struct batadv_priv *bat_priv);
+bool batadv_forw_packet_steal(struct batadv_forw_packet *packet, spinlock_t *l);
+void batadv_forw_packet_ogmv1_queue(struct batadv_priv *bat_priv,
+                                   struct batadv_forw_packet *forw_packet,
+                                   unsigned long send_time);
 
 int batadv_send_skb_to_orig(struct sk_buff *skb,
                            struct batadv_orig_node *orig_node,
index 3cae8f4..447f949 100644 (file)
@@ -646,6 +646,7 @@ bool batadv_tt_local_add(struct net_device *soft_iface, const u8 *addr,
        struct net *net = dev_net(soft_iface);
        struct batadv_softif_vlan *vlan;
        struct net_device *in_dev = NULL;
+       struct batadv_hard_iface *in_hardif = NULL;
        struct hlist_head *head;
        struct batadv_tt_orig_list_entry *orig_entry;
        int hash_added, table_size, packet_size_max;
@@ -657,6 +658,9 @@ bool batadv_tt_local_add(struct net_device *soft_iface, const u8 *addr,
        if (ifindex != BATADV_NULL_IFINDEX)
                in_dev = dev_get_by_index(net, ifindex);
 
+       if (in_dev)
+               in_hardif = batadv_hardif_get_by_netdev(in_dev);
+
        tt_local = batadv_tt_local_hash_find(bat_priv, addr, vid);
 
        if (!is_multicast_ether_addr(addr))
@@ -730,7 +734,7 @@ bool batadv_tt_local_add(struct net_device *soft_iface, const u8 *addr,
         */
        tt_local->common.flags = BATADV_TT_CLIENT_NEW;
        tt_local->common.vid = vid;
-       if (batadv_is_wifi_netdev(in_dev))
+       if (batadv_is_wifi_hardif(in_hardif))
                tt_local->common.flags |= BATADV_TT_CLIENT_WIFI;
        kref_init(&tt_local->common.refcount);
        tt_local->last_seen = jiffies;
@@ -790,7 +794,7 @@ check_roaming:
         */
        remote_flags = tt_local->common.flags & BATADV_TT_REMOTE_MASK;
 
-       if (batadv_is_wifi_netdev(in_dev))
+       if (batadv_is_wifi_hardif(in_hardif))
                tt_local->common.flags |= BATADV_TT_CLIENT_WIFI;
        else
                tt_local->common.flags &= ~BATADV_TT_CLIENT_WIFI;
@@ -814,6 +818,8 @@ check_roaming:
 
        ret = true;
 out:
+       if (in_hardif)
+               batadv_hardif_put(in_hardif);
        if (in_dev)
                dev_put(in_dev);
        if (tt_local)
index 98ebac0..e913aee 100644 (file)
@@ -119,11 +119,27 @@ struct batadv_hard_iface_bat_v {
 };
 
 /**
+ * enum batadv_hard_iface_wifi_flags - Flags describing the wifi configuration
+ *  of a batadv_hard_iface
+ * @BATADV_HARDIF_WIFI_WEXT_DIRECT: it is a wext wifi device
+ * @BATADV_HARDIF_WIFI_CFG80211_DIRECT: it is a cfg80211 wifi device
+ * @BATADV_HARDIF_WIFI_WEXT_INDIRECT: link device is a wext wifi device
+ * @BATADV_HARDIF_WIFI_CFG80211_INDIRECT: link device is a cfg80211 wifi device
+ */
+enum batadv_hard_iface_wifi_flags {
+       BATADV_HARDIF_WIFI_WEXT_DIRECT = BIT(0),
+       BATADV_HARDIF_WIFI_CFG80211_DIRECT = BIT(1),
+       BATADV_HARDIF_WIFI_WEXT_INDIRECT = BIT(2),
+       BATADV_HARDIF_WIFI_CFG80211_INDIRECT = BIT(3),
+};
+
+/**
  * struct batadv_hard_iface - network device known to batman-adv
  * @list: list node for batadv_hardif_list
  * @if_num: identificator of the interface
  * @if_status: status of the interface for batman-adv
  * @num_bcasts: number of payload re-broadcasts on this interface (ARQ)
+ * @wifi_flags: flags whether this is (directly or indirectly) a wifi interface
  * @net_dev: pointer to the net_device
  * @hardif_obj: kobject of the per interface sysfs "mesh" directory
  * @refcount: number of contexts the object is used
@@ -142,6 +158,7 @@ struct batadv_hard_iface {
        s16 if_num;
        char if_status;
        u8 num_bcasts;
+       u32 wifi_flags;
        struct net_device *net_dev;
        struct kobject *hardif_obj;
        struct kref refcount;
@@ -1368,6 +1385,7 @@ struct batadv_skb_cb {
 /**
  * struct batadv_forw_packet - structure for bcast packets to be sent/forwarded
  * @list: list node for batadv_priv::forw_{bat,bcast}_list
+ * @cleanup_list: list node for purging functions
  * @send_time: execution time for delayed_work (packet sending)
  * @own: bool for locally generated packets (local OGMs are re-scheduled after
  *  sending)
@@ -1384,6 +1402,7 @@ struct batadv_skb_cb {
  */
 struct batadv_forw_packet {
        struct hlist_node list;
+       struct hlist_node cleanup_list;
        unsigned long send_time;
        u8 own;
        struct sk_buff *skb;