Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/net/batman-adv/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 124 kB image not shown  

Quelle  translation-table.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) B.A.T.M.A.N. contributors:
 *
 * Marek Lindner, Simon Wunderlich, Antonio Quartulli
 */


#include "translation-table.h"
#include "main.h"

#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/build_bug.h>
#include <linux/byteorder/generic.h>
#include <linux/cache.h>
#include <linux/compiler.h>
#include <linux/container_of.h>
#include <linux/crc32.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/gfp.h>
#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/jhash.h>
#include <linux/jiffies.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/lockdep.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/netlink.h>
#include <linux/overflow.h>
#include <linux/rculist.h>
#include <linux/rcupdate.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/workqueue.h>
#include <net/genetlink.h>
#include <net/netlink.h>
#include <uapi/linux/batadv_packet.h>
#include <uapi/linux/batman_adv.h>

#include "bridge_loop_avoidance.h"
#include "hard-interface.h"
#include "hash.h"
#include "log.h"
#include "mesh-interface.h"
#include "netlink.h"
#include "originator.h"
#include "tvlv.h"

static struct kmem_cache *batadv_tl_cache __read_mostly;
static struct kmem_cache *batadv_tg_cache __read_mostly;
static struct kmem_cache *batadv_tt_orig_cache __read_mostly;
static struct kmem_cache *batadv_tt_change_cache __read_mostly;
static struct kmem_cache *batadv_tt_req_cache __read_mostly;
static struct kmem_cache *batadv_tt_roam_cache __read_mostly;

/* hash class keys */
static struct lock_class_key batadv_tt_local_hash_lock_class_key;
static struct lock_class_key batadv_tt_global_hash_lock_class_key;

static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
     unsigned short vid,
     struct batadv_orig_node *orig_node);
static void batadv_tt_purge(struct work_struct *work);
static void
batadv_tt_global_del_orig_list(struct batadv_tt_global_entry *tt_global_entry);
static void batadv_tt_global_del(struct batadv_priv *bat_priv,
     struct batadv_orig_node *orig_node,
     const unsigned char *addr,
     unsigned short vid, const char *message,
     bool roaming);

/**
 * batadv_compare_tt() - check if two TT entries are the same
 * @node: the list element pointer of the first TT entry
 * @data2: pointer to the tt_common_entry of the second TT entry
 *
 * Compare the MAC address and the VLAN ID of the two TT entries and check if
 * they are the same TT client.
 * Return: true if the two TT clients are the same, false otherwise
 */

static bool batadv_compare_tt(const struct hlist_node *node, const void *data2)
{
 const void *data1 = container_of(node, struct batadv_tt_common_entry,
      hash_entry);
 const struct batadv_tt_common_entry *tt1 = data1;
 const struct batadv_tt_common_entry *tt2 = data2;

 return (tt1->vid == tt2->vid) && batadv_compare_eth(data1, data2);
}

/**
 * batadv_choose_tt() - return the index of the tt entry in the hash table
 * @data: pointer to the tt_common_entry object to map
 * @size: the size of the hash table
 *
 * Return: the hash index where the object represented by 'data' should be
 * stored at.
 */

static inline u32 batadv_choose_tt(const void *data, u32 size)
{
 const struct batadv_tt_common_entry *tt;
 u32 hash = 0;

 tt = data;
 hash = jhash(&tt->addr, ETH_ALEN, hash);
 hash = jhash(&tt->vid, sizeof(tt->vid), hash);

 return hash % size;
}

/**
 * batadv_tt_hash_find() - look for a client in the given hash table
 * @hash: the hash table to search
 * @addr: the mac address of the client to look for
 * @vid: VLAN identifier
 *
 * Return: a pointer to the tt_common struct belonging to the searched client if
 * found, NULL otherwise.
 */

static struct batadv_tt_common_entry *
batadv_tt_hash_find(struct batadv_hashtable *hash, const u8 *addr,
      unsigned short vid)
{
 struct hlist_head *head;
 struct batadv_tt_common_entry to_search, *tt, *tt_tmp = NULL;
 u32 index;

 if (!hash)
  return NULL;

 ether_addr_copy(to_search.addr, addr);
 to_search.vid = vid;

 index = batadv_choose_tt(&to_search, hash->size);
 head = &hash->table[index];

 rcu_read_lock();
 hlist_for_each_entry_rcu(tt, head, hash_entry) {
  if (!batadv_compare_eth(tt, addr))
   continue;

  if (tt->vid != vid)
   continue;

  if (!kref_get_unless_zero(&tt->refcount))
   continue;

  tt_tmp = tt;
  break;
 }
 rcu_read_unlock();

 return tt_tmp;
}

/**
 * batadv_tt_local_hash_find() - search the local table for a given client
 * @bat_priv: the bat priv with all the mesh interface information
 * @addr: the mac address of the client to look for
 * @vid: VLAN identifier
 *
 * Return: a pointer to the corresponding tt_local_entry struct if the client is
 * found, NULL otherwise.
 */

static struct batadv_tt_local_entry *
batadv_tt_local_hash_find(struct batadv_priv *bat_priv, const u8 *addr,
     unsigned short vid)
{
 struct batadv_tt_common_entry *tt_common_entry;
 struct batadv_tt_local_entry *tt_local_entry = NULL;

 tt_common_entry = batadv_tt_hash_find(bat_priv->tt.local_hash, addr,
           vid);
 if (tt_common_entry)
  tt_local_entry = container_of(tt_common_entry,
           struct batadv_tt_local_entry,
           common);
 return tt_local_entry;
}

/**
 * batadv_tt_global_hash_find() - search the global table for a given client
 * @bat_priv: the bat priv with all the mesh interface information
 * @addr: the mac address of the client to look for
 * @vid: VLAN identifier
 *
 * Return: a pointer to the corresponding tt_global_entry struct if the client
 * is found, NULL otherwise.
 */

struct batadv_tt_global_entry *
batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const u8 *addr,
      unsigned short vid)
{
 struct batadv_tt_common_entry *tt_common_entry;
 struct batadv_tt_global_entry *tt_global_entry = NULL;

 tt_common_entry = batadv_tt_hash_find(bat_priv->tt.global_hash, addr,
           vid);
 if (tt_common_entry)
  tt_global_entry = container_of(tt_common_entry,
            struct batadv_tt_global_entry,
            common);
 return tt_global_entry;
}

/**
 * batadv_tt_local_entry_release() - release tt_local_entry from lists and queue
 *  for free after rcu grace period
 * @ref: kref pointer of the nc_node
 */

static void batadv_tt_local_entry_release(struct kref *ref)
{
 struct batadv_tt_local_entry *tt_local_entry;

 tt_local_entry = container_of(ref, struct batadv_tt_local_entry,
          common.refcount);

 batadv_meshif_vlan_put(tt_local_entry->vlan);

 kfree_rcu(tt_local_entry, common.rcu);
}

/**
 * batadv_tt_local_entry_put() - decrement the tt_local_entry refcounter and
 *  possibly release it
 * @tt_local_entry: tt_local_entry to be free'd
 */

static void
batadv_tt_local_entry_put(struct batadv_tt_local_entry *tt_local_entry)
{
 if (!tt_local_entry)
  return;

 kref_put(&tt_local_entry->common.refcount,
   batadv_tt_local_entry_release);
}

/**
 * batadv_tt_global_entry_release() - release tt_global_entry from lists and
 *  queue for free after rcu grace period
 * @ref: kref pointer of the nc_node
 */

void batadv_tt_global_entry_release(struct kref *ref)
{
 struct batadv_tt_global_entry *tt_global_entry;

 tt_global_entry = container_of(ref, struct batadv_tt_global_entry,
           common.refcount);

 batadv_tt_global_del_orig_list(tt_global_entry);

 kfree_rcu(tt_global_entry, common.rcu);
}

/**
 * batadv_tt_global_hash_count() - count the number of orig entries
 * @bat_priv: the bat priv with all the mesh interface information
 * @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 our self).
 */

int batadv_tt_global_hash_count(struct batadv_priv *bat_priv,
    const u8 *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_put(tt_global_entry);

 return count;
}

/**
 * batadv_tt_local_size_mod() - change the size by v of the local table
 *  identified by vid
 * @bat_priv: the bat priv with all the mesh interface information
 * @vid: the VLAN identifier of the sub-table to change
 * @v: the amount to sum to the local table size
 */

static void batadv_tt_local_size_mod(struct batadv_priv *bat_priv,
         unsigned short vid, int v)
{
 struct batadv_meshif_vlan *vlan;

 vlan = batadv_meshif_vlan_get(bat_priv, vid);
 if (!vlan)
  return;

 atomic_add(v, &vlan->tt.num_entries);

 batadv_meshif_vlan_put(vlan);
}

/**
 * batadv_tt_local_size_inc() - increase by one the local table size for the
 *  given vid
 * @bat_priv: the bat priv with all the mesh interface information
 * @vid: the VLAN identifier
 */

static void batadv_tt_local_size_inc(struct batadv_priv *bat_priv,
         unsigned short vid)
{
 batadv_tt_local_size_mod(bat_priv, vid, 1);
}

/**
 * batadv_tt_local_size_dec() - decrease by one the local table size for the
 *  given vid
 * @bat_priv: the bat priv with all the mesh interface information
 * @vid: the VLAN identifier
 */

static void batadv_tt_local_size_dec(struct batadv_priv *bat_priv,
         unsigned short vid)
{
 batadv_tt_local_size_mod(bat_priv, vid, -1);
}

/**
 * batadv_tt_global_size_mod() - change the size by v of the global table
 *  for orig_node identified by vid
 * @orig_node: the originator for which the table has to be modified
 * @vid: the VLAN identifier
 * @v: the amount to sum to the global table size
 */

static void batadv_tt_global_size_mod(struct batadv_orig_node *orig_node,
          unsigned short vid, int v)
{
 struct batadv_orig_node_vlan *vlan;

 vlan = batadv_orig_node_vlan_new(orig_node, vid);
 if (!vlan)
  return;

 if (atomic_add_return(v, &vlan->tt.num_entries) == 0) {
  spin_lock_bh(&orig_node->vlan_list_lock);
  if (!hlist_unhashed(&vlan->list)) {
   hlist_del_init_rcu(&vlan->list);
   batadv_orig_node_vlan_put(vlan);
  }
  spin_unlock_bh(&orig_node->vlan_list_lock);
 }

 batadv_orig_node_vlan_put(vlan);
}

/**
 * batadv_tt_global_size_inc() - increase by one the global table size for the
 *  given vid
 * @orig_node: the originator which global table size has to be decreased
 * @vid: the vlan identifier
 */

static void batadv_tt_global_size_inc(struct batadv_orig_node *orig_node,
          unsigned short vid)
{
 batadv_tt_global_size_mod(orig_node, vid, 1);
}

/**
 * batadv_tt_global_size_dec() - decrease by one the global table size for the
 *  given vid
 * @orig_node: the originator which global table size has to be decreased
 * @vid: the vlan identifier
 */

static void batadv_tt_global_size_dec(struct batadv_orig_node *orig_node,
          unsigned short vid)
{
 batadv_tt_global_size_mod(orig_node, vid, -1);
}

/**
 * batadv_tt_orig_list_entry_release() - release tt orig entry from lists and
 *  queue for free after rcu grace period
 * @ref: kref pointer of the tt orig entry
 */

static void batadv_tt_orig_list_entry_release(struct kref *ref)
{
 struct batadv_tt_orig_list_entry *orig_entry;

 orig_entry = container_of(ref, struct batadv_tt_orig_list_entry,
      refcount);

 batadv_orig_node_put(orig_entry->orig_node);
 kfree_rcu(orig_entry, rcu);
}

/**
 * batadv_tt_orig_list_entry_put() - decrement the tt orig entry refcounter and
 *  possibly release it
 * @orig_entry: tt orig entry to be free'd
 */

static void
batadv_tt_orig_list_entry_put(struct batadv_tt_orig_list_entry *orig_entry)
{
 if (!orig_entry)
  return;

 kref_put(&orig_entry->refcount, batadv_tt_orig_list_entry_release);
}

/**
 * batadv_tt_local_event() - store a local TT event (ADD/DEL)
 * @bat_priv: the bat priv with all the mesh interface information
 * @tt_local_entry: the TT entry involved in the event
 * @event_flags: flags to store in the event structure
 */

static void batadv_tt_local_event(struct batadv_priv *bat_priv,
      struct batadv_tt_local_entry *tt_local_entry,
      u8 event_flags)
{
 struct batadv_tt_change_node *tt_change_node, *entry, *safe;
 struct batadv_tt_common_entry *common = &tt_local_entry->common;
 u8 flags = common->flags | event_flags;
 bool del_op_requested, del_op_entry;
 size_t changes;

 tt_change_node = kmem_cache_alloc(batadv_tt_change_cache, GFP_ATOMIC);
 if (!tt_change_node)
  return;

 tt_change_node->change.flags = flags;
 memset(tt_change_node->change.reserved, 0,
        sizeof(tt_change_node->change.reserved));
 ether_addr_copy(tt_change_node->change.addr, common->addr);
 tt_change_node->change.vid = htons(common->vid);

 del_op_requested = flags & BATADV_TT_CLIENT_DEL;

 /* check for ADD+DEL, DEL+ADD, ADD+ADD or DEL+DEL events */
 spin_lock_bh(&bat_priv->tt.changes_list_lock);
 changes = READ_ONCE(bat_priv->tt.local_changes);
 list_for_each_entry_safe(entry, safe, &bat_priv->tt.changes_list,
     list) {
  if (!batadv_compare_eth(entry->change.addr, common->addr))
   continue;

  del_op_entry = entry->change.flags & BATADV_TT_CLIENT_DEL;
  if (del_op_requested != del_op_entry) {
   /* DEL+ADD in the same orig interval have no effect and
 * can be removed to avoid silly behaviour on the
 * receiver side. The  other way around (ADD+DEL) can
 * happen in case of roaming of  a client still in the
 * NEW state. Roaming of NEW clients is now possible due
 * to automatically recognition of "temporary" clients
 */

   list_del(&entry->list);
   kmem_cache_free(batadv_tt_change_cache, entry);
   changes--;
  } else {
   /* this is a second add or del in the same originator
 * interval. It could mean that flags have been changed
 * (e.g. double add): update them
 */

   entry->change.flags = flags;
  }

  kmem_cache_free(batadv_tt_change_cache, tt_change_node);
  goto update_changes;
 }

 /* track the change in the OGMinterval list */
 list_add_tail(&tt_change_node->list, &bat_priv->tt.changes_list);
 changes++;

update_changes:
 WRITE_ONCE(bat_priv->tt.local_changes, changes);
 spin_unlock_bh(&bat_priv->tt.changes_list_lock);
}

/**
 * batadv_tt_len() - compute length in bytes of given number of tt changes
 * @changes_num: number of tt changes
 *
 * Return: computed length in bytes.
 */

static int batadv_tt_len(int changes_num)
{
 return changes_num * sizeof(struct batadv_tvlv_tt_change);
}

/**
 * batadv_tt_entries() - compute the number of entries fitting in tt_len bytes
 * @tt_len: available space
 *
 * Return: the number of entries.
 */

static u16 batadv_tt_entries(u16 tt_len)
{
 return tt_len / batadv_tt_len(1);
}

/**
 * batadv_tt_local_table_transmit_size() - calculates the local translation
 *  table size when transmitted over the air
 * @bat_priv: the bat priv with all the mesh interface information
 *
 * Return: local translation table size in bytes.
 */

static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv)
{
 u16 num_vlan = 0;
 u16 tt_local_entries = 0;
 struct batadv_meshif_vlan *vlan;
 int hdr_size;

 rcu_read_lock();
 hlist_for_each_entry_rcu(vlan, &bat_priv->meshif_vlan_list, list) {
  num_vlan++;
  tt_local_entries += atomic_read(&vlan->tt.num_entries);
 }
 rcu_read_unlock();

 /* header size of tvlv encapsulated tt response payload */
 hdr_size = sizeof(struct batadv_unicast_tvlv_packet);
 hdr_size += sizeof(struct batadv_tvlv_hdr);
 hdr_size += sizeof(struct batadv_tvlv_tt_data);
 hdr_size += num_vlan * sizeof(struct batadv_tvlv_tt_vlan_data);

 return hdr_size + batadv_tt_len(tt_local_entries);
}

static int batadv_tt_local_init(struct batadv_priv *bat_priv)
{
 if (bat_priv->tt.local_hash)
  return 0;

 bat_priv->tt.local_hash = batadv_hash_new(1024);

 if (!bat_priv->tt.local_hash)
  return -ENOMEM;

 batadv_hash_set_lock_class(bat_priv->tt.local_hash,
       &batadv_tt_local_hash_lock_class_key);

 return 0;
}

static void batadv_tt_global_free(struct batadv_priv *bat_priv,
      struct batadv_tt_global_entry *tt_global,
      const char *message)
{
 struct batadv_tt_global_entry *tt_removed_entry;
 struct hlist_node *tt_removed_node;

 batadv_dbg(BATADV_DBG_TT, bat_priv,
     "Deleting global tt entry %pM (vid: %d): %s\n",
     tt_global->common.addr,
     batadv_print_vid(tt_global->common.vid), message);

 tt_removed_node = batadv_hash_remove(bat_priv->tt.global_hash,
          batadv_compare_tt,
          batadv_choose_tt,
          &tt_global->common);
 if (!tt_removed_node)
  return;

 /* drop reference of remove hash entry */
 tt_removed_entry = hlist_entry(tt_removed_node,
           struct batadv_tt_global_entry,
           common.hash_entry);
 batadv_tt_global_entry_put(tt_removed_entry);
}

/**
 * batadv_tt_local_add() - add a new client to the local table or update an
 *  existing client
 * @mesh_iface: netdev struct of the mesh interface
 * @addr: the mac address of the client to add
 * @vid: VLAN identifier
 * @ifindex: index of the interface where the client is connected to (useful to
 *  identify wireless clients)
 * @mark: the value contained in the skb->mark field of the received packet (if
 *  any)
 *
 * Return: true if the client was successfully added, false otherwise.
 */

bool batadv_tt_local_add(struct net_device *mesh_iface, const u8 *addr,
    unsigned short vid, int ifindex, u32 mark)
{
 struct batadv_priv *bat_priv = netdev_priv(mesh_iface);
 struct batadv_tt_local_entry *tt_local;
 struct batadv_tt_global_entry *tt_global = NULL;
 struct net *net = dev_net(mesh_iface);
 struct batadv_meshif_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;
 bool ret = false;
 bool roamed_back = false;
 u8 remote_flags;
 u32 match_mark;

 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))
  tt_global = batadv_tt_global_hash_find(bat_priv, addr, vid);

 if (tt_local) {
  tt_local->last_seen = jiffies;
  if (tt_local->common.flags & BATADV_TT_CLIENT_PENDING) {
   batadv_dbg(BATADV_DBG_TT, bat_priv,
       "Re-adding pending client %pM (vid: %d)\n",
       addr, batadv_print_vid(vid));
   /* whatever the reason why the PENDING flag was set,
 * this is a client which was enqueued to be removed in
 * this orig_interval. Since it popped up again, the
 * flag can be reset like it was never enqueued
 */

   tt_local->common.flags &= ~BATADV_TT_CLIENT_PENDING;
   goto add_event;
  }

  if (tt_local->common.flags & BATADV_TT_CLIENT_ROAM) {
   batadv_dbg(BATADV_DBG_TT, bat_priv,
       "Roaming client %pM (vid: %d) came back to its original location\n",
       addr, batadv_print_vid(vid));
   /* the ROAM flag is set because this client roamed away
 * and the node got a roaming_advertisement message. Now
 * that the client popped up again at its original
 * location such flag can be unset
 */

   tt_local->common.flags &= ~BATADV_TT_CLIENT_ROAM;
   roamed_back = true;
  }
  goto check_roaming;
 }

 /* Ignore the client if we cannot send it in a full table response. */
 table_size = batadv_tt_local_table_transmit_size(bat_priv);
 table_size += batadv_tt_len(1);
 packet_size_max = atomic_read(&bat_priv->packet_size_max);
 if (table_size > packet_size_max) {
  net_ratelimited_function(batadv_info, mesh_iface,
      "Local translation table size (%i) exceeds maximum packet size (%i); Ignoring new local tt entry: %pM\n",
      table_size, packet_size_max, addr);
  goto out;
 }

 tt_local = kmem_cache_alloc(batadv_tl_cache, GFP_ATOMIC);
 if (!tt_local)
  goto out;

 /* increase the refcounter of the related vlan */
 vlan = batadv_meshif_vlan_get(bat_priv, vid);
 if (!vlan) {
  net_ratelimited_function(batadv_info, mesh_iface,
      "adding TT local entry %pM to non-existent VLAN %d\n",
      addr, batadv_print_vid(vid));
  kmem_cache_free(batadv_tl_cache, tt_local);
  tt_local = NULL;
  goto out;
 }

 batadv_dbg(BATADV_DBG_TT, bat_priv,
     "Creating new local tt entry: %pM (vid: %d, ttvn: %d)\n",
     addr, batadv_print_vid(vid),
     (u8)atomic_read(&bat_priv->tt.vn));

 ether_addr_copy(tt_local->common.addr, addr);
 /* The local entry has to be marked as NEW to avoid to send it in
 * a full table response going out before the next ttvn increment
 * (consistency check)
 */

 tt_local->common.flags = BATADV_TT_CLIENT_NEW;
 tt_local->common.vid = vid;
 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;
 tt_local->common.added_at = tt_local->last_seen;
 tt_local->vlan = vlan;

 /* the batman interface mac and multicast addresses should never be
 * purged
 */

 if (batadv_compare_eth(addr, mesh_iface->dev_addr) ||
     is_multicast_ether_addr(addr))
  tt_local->common.flags |= BATADV_TT_CLIENT_NOPURGE;

 kref_get(&tt_local->common.refcount);
 hash_added = batadv_hash_add(bat_priv->tt.local_hash, batadv_compare_tt,
         batadv_choose_tt, &tt_local->common,
         &tt_local->common.hash_entry);

 if (unlikely(hash_added != 0)) {
  /* remove the reference for the hash */
  batadv_tt_local_entry_put(tt_local);
  goto out;
 }

add_event:
 batadv_tt_local_event(bat_priv, tt_local, BATADV_NO_FLAGS);

check_roaming:
 /* Check whether it is a roaming, but don't do anything if the roaming
 * process has already been handled
 */

 if (tt_global && !(tt_global->common.flags & BATADV_TT_CLIENT_ROAM)) {
  /* These node are probably going to update their tt table */
  head = &tt_global->orig_list;
  rcu_read_lock();
  hlist_for_each_entry_rcu(orig_entry, head, list) {
   batadv_send_roam_adv(bat_priv, tt_global->common.addr,
          tt_global->common.vid,
          orig_entry->orig_node);
  }
  rcu_read_unlock();
  if (roamed_back) {
   batadv_tt_global_free(bat_priv, tt_global,
           "Roaming canceled");
  } else {
   /* The global entry has to be marked as ROAMING and
 * has to be kept for consistency purpose
 */

   tt_global->common.flags |= BATADV_TT_CLIENT_ROAM;
   tt_global->roam_at = jiffies;
  }
 }

 /* store the current remote flags before altering them. This helps
 * understanding is flags are changing or not
 */

 remote_flags = tt_local->common.flags & BATADV_TT_REMOTE_MASK;

 if (batadv_is_wifi_hardif(in_hardif))
  tt_local->common.flags |= BATADV_TT_CLIENT_WIFI;
 else
  tt_local->common.flags &= ~BATADV_TT_CLIENT_WIFI;

 /* check the mark in the skb: if it's equal to the configured
 * isolation_mark, it means the packet is coming from an isolated
 * non-mesh client
 */

 match_mark = (mark & bat_priv->isolation_mark_mask);
 if (bat_priv->isolation_mark_mask &&
     match_mark == bat_priv->isolation_mark)
  tt_local->common.flags |= BATADV_TT_CLIENT_ISOLA;
 else
  tt_local->common.flags &= ~BATADV_TT_CLIENT_ISOLA;

 /* if any "dynamic" flag has been modified, resend an ADD event for this
 * entry so that all the nodes can get the new flags
 */

 if (remote_flags ^ (tt_local->common.flags & BATADV_TT_REMOTE_MASK))
  batadv_tt_local_event(bat_priv, tt_local, BATADV_NO_FLAGS);

 ret = true;
out:
 batadv_hardif_put(in_hardif);
 dev_put(in_dev);
 batadv_tt_local_entry_put(tt_local);
 batadv_tt_global_entry_put(tt_global);
 return ret;
}

/**
 * batadv_tt_prepare_tvlv_global_data() - prepare the TVLV TT header to send
 *  within a TT Response directed to another node
 * @orig_node: originator for which the TT data has to be prepared
 * @tt_data: uninitialised pointer to the address of the TVLV buffer
 * @tt_change: uninitialised pointer to the address of the area where the TT
 *  changed can be stored
 * @tt_len: pointer to the length to reserve to the tt_change. if -1 this
 *  function reserves the amount of space needed to send the entire global TT
 *  table. In case of success the value is updated with the real amount of
 *  reserved bytes
 * Allocate the needed amount of memory for the entire TT TVLV and write its
 * header made up of one tvlv_tt_data object and a series of tvlv_tt_vlan_data
 * objects, one per active VLAN served by the originator node.
 *
 * Return: the size of the allocated buffer or 0 in case of failure.
 */

static u16
batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
       struct batadv_tvlv_tt_data **tt_data,
       struct batadv_tvlv_tt_change **tt_change,
       s32 *tt_len)
{
 u16 num_vlan = 0;
 u16 num_entries = 0;
 u16 change_offset;
 u16 tvlv_len;
 struct batadv_tvlv_tt_vlan_data *tt_vlan;
 struct batadv_orig_node_vlan *vlan;
 u8 *tt_change_ptr;

 spin_lock_bh(&orig_node->vlan_list_lock);
 hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
  num_vlan++;
  num_entries += atomic_read(&vlan->tt.num_entries);
 }

 change_offset = struct_size(*tt_data, vlan_data, num_vlan);

 /* if tt_len is negative, allocate the space needed by the full table */
 if (*tt_len < 0)
  *tt_len = batadv_tt_len(num_entries);

 tvlv_len = *tt_len;
 tvlv_len += change_offset;

 *tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
 if (!*tt_data) {
  *tt_len = 0;
  goto out;
 }

 (*tt_data)->flags = BATADV_NO_FLAGS;
 (*tt_data)->ttvn = atomic_read(&orig_node->last_ttvn);
 (*tt_data)->num_vlan = htons(num_vlan);

 tt_vlan = (*tt_data)->vlan_data;
 hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
  tt_vlan->vid = htons(vlan->vid);
  tt_vlan->crc = htonl(vlan->tt.crc);
  tt_vlan->reserved = 0;

  tt_vlan++;
 }

 tt_change_ptr = (u8 *)*tt_data + change_offset;
 *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;

out:
 spin_unlock_bh(&orig_node->vlan_list_lock);
 return tvlv_len;
}

/**
 * batadv_tt_prepare_tvlv_local_data() - allocate and prepare the TT TVLV for
 *  this node
 * @bat_priv: the bat priv with all the mesh interface information
 * @tt_data: uninitialised pointer to the address of the TVLV buffer
 * @tt_change: uninitialised pointer to the address of the area where the TT
 *  changes can be stored
 * @tt_len: pointer to the length to reserve to the tt_change. if -1 this
 *  function reserves the amount of space needed to send the entire local TT
 *  table. In case of success the value is updated with the real amount of
 *  reserved bytes
 *
 * Allocate the needed amount of memory for the entire TT TVLV and write its
 * header made up by one tvlv_tt_data object and a series of tvlv_tt_vlan_data
 * objects, one per active VLAN.
 *
 * Return: the size of the allocated buffer or 0 in case of failure.
 */

static u16
batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
      struct batadv_tvlv_tt_data **tt_data,
      struct batadv_tvlv_tt_change **tt_change,
      s32 *tt_len)
{
 struct batadv_tvlv_tt_vlan_data *tt_vlan;
 struct batadv_meshif_vlan *vlan;
 u16 num_vlan = 0;
 u16 vlan_entries = 0;
 u16 total_entries = 0;
 u16 tvlv_len;
 u8 *tt_change_ptr;
 int change_offset;

 spin_lock_bh(&bat_priv->meshif_vlan_list_lock);
 hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
  vlan_entries = atomic_read(&vlan->tt.num_entries);
  if (vlan_entries < 1)
   continue;

  num_vlan++;
  total_entries += vlan_entries;
 }

 change_offset = struct_size(*tt_data, vlan_data, num_vlan);

 /* if tt_len is negative, allocate the space needed by the full table */
 if (*tt_len < 0)
  *tt_len = batadv_tt_len(total_entries);

 tvlv_len = *tt_len;
 tvlv_len += change_offset;

 *tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
 if (!*tt_data) {
  tvlv_len = 0;
  goto out;
 }

 (*tt_data)->flags = BATADV_NO_FLAGS;
 (*tt_data)->ttvn = atomic_read(&bat_priv->tt.vn);
 (*tt_data)->num_vlan = htons(num_vlan);

 tt_vlan = (*tt_data)->vlan_data;
 hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
  vlan_entries = atomic_read(&vlan->tt.num_entries);
  if (vlan_entries < 1)
   continue;

  tt_vlan->vid = htons(vlan->vid);
  tt_vlan->crc = htonl(vlan->tt.crc);
  tt_vlan->reserved = 0;

  tt_vlan++;
 }

 tt_change_ptr = (u8 *)*tt_data + change_offset;
 *tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;

out:
 spin_unlock_bh(&bat_priv->meshif_vlan_list_lock);
 return tvlv_len;
}

/**
 * batadv_tt_tvlv_container_update() - update the translation table tvlv
 *  container after local tt changes have been committed
 * @bat_priv: the bat priv with all the mesh interface information
 */

static void batadv_tt_tvlv_container_update(struct batadv_priv *bat_priv)
{
 struct batadv_tt_change_node *entry, *safe;
 struct batadv_tvlv_tt_data *tt_data;
 struct batadv_tvlv_tt_change *tt_change;
 int tt_diff_len, tt_change_len = 0;
 int tt_diff_entries_num = 0;
 int tt_diff_entries_count = 0;
 bool drop_changes = false;
 size_t tt_extra_len = 0;
 u16 tvlv_len;

 tt_diff_entries_num = READ_ONCE(bat_priv->tt.local_changes);
 tt_diff_len = batadv_tt_len(tt_diff_entries_num);

 /* if we have too many changes for one packet don't send any
 * and wait for the tt table request so we can reply with the full
 * (fragmented) table.
 *
 * The local change history should still be cleaned up so the next
 * TT round can start again with a clean state.
 */

 if (tt_diff_len > bat_priv->mesh_iface->mtu) {
  tt_diff_len = 0;
  tt_diff_entries_num = 0;
  drop_changes = true;
 }

 tvlv_len = batadv_tt_prepare_tvlv_local_data(bat_priv, &tt_data,
           &tt_change, &tt_diff_len);
 if (!tvlv_len)
  return;

 tt_data->flags = BATADV_TT_OGM_DIFF;

 if (!drop_changes && tt_diff_len == 0)
  goto container_register;

 spin_lock_bh(&bat_priv->tt.changes_list_lock);
 WRITE_ONCE(bat_priv->tt.local_changes, 0);

 list_for_each_entry_safe(entry, safe, &bat_priv->tt.changes_list,
     list) {
  if (tt_diff_entries_count < tt_diff_entries_num) {
   memcpy(tt_change + tt_diff_entries_count,
          &entry->change,
          sizeof(struct batadv_tvlv_tt_change));
   tt_diff_entries_count++;
  }
  list_del(&entry->list);
  kmem_cache_free(batadv_tt_change_cache, entry);
 }
 spin_unlock_bh(&bat_priv->tt.changes_list_lock);

 tt_extra_len = batadv_tt_len(tt_diff_entries_num -
         tt_diff_entries_count);

 /* Keep the buffer for possible tt_request */
 spin_lock_bh(&bat_priv->tt.last_changeset_lock);
 kfree(bat_priv->tt.last_changeset);
 bat_priv->tt.last_changeset_len = 0;
 bat_priv->tt.last_changeset = NULL;
 tt_change_len = batadv_tt_len(tt_diff_entries_count);
 /* check whether this new OGM has no changes due to size problems */
 if (tt_diff_entries_count > 0) {
  tt_diff_len -= tt_extra_len;
  /* if kmalloc() fails we will reply with the full table
 * instead of providing the diff
 */

  bat_priv->tt.last_changeset = kzalloc(tt_diff_len, GFP_ATOMIC);
  if (bat_priv->tt.last_changeset) {
   memcpy(bat_priv->tt.last_changeset,
          tt_change, tt_change_len);
   bat_priv->tt.last_changeset_len = tt_diff_len;
  }
 }
 spin_unlock_bh(&bat_priv->tt.last_changeset_lock);

 /* Remove extra packet space for OGM */
 tvlv_len -= tt_extra_len;
container_register:
 batadv_tvlv_container_register(bat_priv, BATADV_TVLV_TT, 1, tt_data,
           tvlv_len);
 kfree(tt_data);
}

/**
 * batadv_tt_local_dump_entry() - Dump one TT local entry into a message
 * @msg :Netlink message to dump into
 * @portid: Port making netlink request
 * @cb: Control block containing additional options
 * @bat_priv: The bat priv with all the mesh interface information
 * @common: tt local & tt global common data
 *
 * Return: Error code, or 0 on success
 */

static int
batadv_tt_local_dump_entry(struct sk_buff *msg, u32 portid,
      struct netlink_callback *cb,
      struct batadv_priv *bat_priv,
      struct batadv_tt_common_entry *common)
{
 void *hdr;
 struct batadv_meshif_vlan *vlan;
 struct batadv_tt_local_entry *local;
 unsigned int last_seen_msecs;
 u32 crc;

 local = container_of(common, struct batadv_tt_local_entry, common);
 last_seen_msecs = jiffies_to_msecs(jiffies - local->last_seen);

 vlan = batadv_meshif_vlan_get(bat_priv, common->vid);
 if (!vlan)
  return 0;

 crc = vlan->tt.crc;

 batadv_meshif_vlan_put(vlan);

 hdr = genlmsg_put(msg, portid, cb->nlh->nlmsg_seq,
     &batadv_netlink_family,  NLM_F_MULTI,
     BATADV_CMD_GET_TRANSTABLE_LOCAL);
 if (!hdr)
  return -ENOBUFS;

 genl_dump_check_consistent(cb, hdr);

 if (nla_put(msg, BATADV_ATTR_TT_ADDRESS, ETH_ALEN, common->addr) ||
     nla_put_u32(msg, BATADV_ATTR_TT_CRC32, crc) ||
     nla_put_u16(msg, BATADV_ATTR_TT_VID, common->vid) ||
     nla_put_u32(msg, BATADV_ATTR_TT_FLAGS, common->flags))
  goto nla_put_failure;

 if (!(common->flags & BATADV_TT_CLIENT_NOPURGE) &&
     nla_put_u32(msg, BATADV_ATTR_LAST_SEEN_MSECS, last_seen_msecs))
  goto nla_put_failure;

 genlmsg_end(msg, hdr);
 return 0;

 nla_put_failure:
 genlmsg_cancel(msg, hdr);
 return -EMSGSIZE;
}

/**
 * batadv_tt_local_dump_bucket() - Dump one TT local bucket into a message
 * @msg: Netlink message to dump into
 * @portid: Port making netlink request
 * @cb: Control block containing additional options
 * @bat_priv: The bat priv with all the mesh interface information
 * @hash: hash to dump
 * @bucket: bucket index to dump
 * @idx_s: Number of entries to skip
 *
 * Return: Error code, or 0 on success
 */

static int
batadv_tt_local_dump_bucket(struct sk_buff *msg, u32 portid,
       struct netlink_callback *cb,
       struct batadv_priv *bat_priv,
       struct batadv_hashtable *hash, unsigned int bucket,
       int *idx_s)
{
 struct batadv_tt_common_entry *common;
 int idx = 0;

 spin_lock_bh(&hash->list_locks[bucket]);
 cb->seq = atomic_read(&hash->generation) << 1 | 1;

 hlist_for_each_entry(common, &hash->table[bucket], hash_entry) {
  if (idx++ < *idx_s)
   continue;

  if (batadv_tt_local_dump_entry(msg, portid, cb, bat_priv,
            common)) {
   spin_unlock_bh(&hash->list_locks[bucket]);
   *idx_s = idx - 1;
   return -EMSGSIZE;
  }
 }
 spin_unlock_bh(&hash->list_locks[bucket]);

 *idx_s = 0;
 return 0;
}

/**
 * batadv_tt_local_dump() - Dump TT local entries into a message
 * @msg: Netlink message to dump into
 * @cb: Parameters from query
 *
 * Return: Error code, or 0 on success
 */

int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb)
{
 struct net_device *mesh_iface;
 struct batadv_priv *bat_priv;
 struct batadv_hard_iface *primary_if = NULL;
 struct batadv_hashtable *hash;
 int ret;
 int bucket = cb->args[0];
 int idx = cb->args[1];
 int portid = NETLINK_CB(cb->skb).portid;

 mesh_iface = batadv_netlink_get_meshif(cb);
 if (IS_ERR(mesh_iface))
  return PTR_ERR(mesh_iface);

 bat_priv = netdev_priv(mesh_iface);

 primary_if = batadv_primary_if_get_selected(bat_priv);
 if (!primary_if || primary_if->if_status != BATADV_IF_ACTIVE) {
  ret = -ENOENT;
  goto out;
 }

 hash = bat_priv->tt.local_hash;

 while (bucket < hash->size) {
  if (batadv_tt_local_dump_bucket(msg, portid, cb, bat_priv,
      hash, bucket, &idx))
   break;

  bucket++;
 }

 ret = msg->len;

 out:
 batadv_hardif_put(primary_if);
 dev_put(mesh_iface);

 cb->args[0] = bucket;
 cb->args[1] = idx;

 return ret;
}

static void
batadv_tt_local_set_pending(struct batadv_priv *bat_priv,
       struct batadv_tt_local_entry *tt_local_entry,
       u16 flags, const char *message)
{
 batadv_tt_local_event(bat_priv, tt_local_entry, flags);

 /* The local client has to be marked as "pending to be removed" but has
 * to be kept in the table in order to send it in a full table
 * response issued before the net ttvn increment (consistency check)
 */

 tt_local_entry->common.flags |= BATADV_TT_CLIENT_PENDING;

 batadv_dbg(BATADV_DBG_TT, bat_priv,
     "Local tt entry (%pM, vid: %d) pending to be removed: %s\n",
     tt_local_entry->common.addr,
     batadv_print_vid(tt_local_entry->common.vid), message);
}

/**
 * batadv_tt_local_remove() - logically remove an entry from the local table
 * @bat_priv: the bat priv with all the mesh interface information
 * @addr: the MAC address of the client to remove
 * @vid: VLAN identifier
 * @message: message to append to the log on deletion
 * @roaming: true if the deletion is due to a roaming event
 *
 * Return: the flags assigned to the local entry before being deleted
 */

u16 batadv_tt_local_remove(struct batadv_priv *bat_priv, const u8 *addr,
      unsigned short vid, const char *message,
      bool roaming)
{
 struct batadv_tt_local_entry *tt_removed_entry;
 struct batadv_tt_local_entry *tt_local_entry;
 u16 flags, curr_flags = BATADV_NO_FLAGS;
 struct hlist_node *tt_removed_node;

 tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid);
 if (!tt_local_entry)
  goto out;

 curr_flags = tt_local_entry->common.flags;

 flags = BATADV_TT_CLIENT_DEL;
 /* if this global entry addition is due to a roaming, the node has to
 * mark the local entry as "roamed" in order to correctly reroute
 * packets later
 */

 if (roaming) {
  flags |= BATADV_TT_CLIENT_ROAM;
  /* mark the local client as ROAMed */
  tt_local_entry->common.flags |= BATADV_TT_CLIENT_ROAM;
 }

 if (!(tt_local_entry->common.flags & BATADV_TT_CLIENT_NEW)) {
  batadv_tt_local_set_pending(bat_priv, tt_local_entry, flags,
         message);
  goto out;
 }
 /* if this client has been added right now, it is possible to
 * immediately purge it
 */

 batadv_tt_local_event(bat_priv, tt_local_entry, BATADV_TT_CLIENT_DEL);

 tt_removed_node = batadv_hash_remove(bat_priv->tt.local_hash,
          batadv_compare_tt,
          batadv_choose_tt,
          &tt_local_entry->common);
 if (!tt_removed_node)
  goto out;

 /* drop reference of remove hash entry */
 tt_removed_entry = hlist_entry(tt_removed_node,
           struct batadv_tt_local_entry,
           common.hash_entry);
 batadv_tt_local_entry_put(tt_removed_entry);

out:
 batadv_tt_local_entry_put(tt_local_entry);

 return curr_flags;
}

/**
 * batadv_tt_local_purge_list() - purge inactive tt local entries
 * @bat_priv: the bat priv with all the mesh interface information
 * @head: pointer to the list containing the local tt entries
 * @timeout: parameter deciding whether a given tt local entry is considered
 *  inactive or not
 */

static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv,
           struct hlist_head *head,
           int timeout)
{
 struct batadv_tt_local_entry *tt_local_entry;
 struct batadv_tt_common_entry *tt_common_entry;
 struct hlist_node *node_tmp;

 hlist_for_each_entry_safe(tt_common_entry, node_tmp, head,
      hash_entry) {
  tt_local_entry = container_of(tt_common_entry,
           struct batadv_tt_local_entry,
           common);
  if (tt_local_entry->common.flags & BATADV_TT_CLIENT_NOPURGE)
   continue;

  /* entry already marked for deletion */
  if (tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING)
   continue;

  if (!batadv_has_timed_out(tt_local_entry->last_seen, timeout))
   continue;

  batadv_tt_local_set_pending(bat_priv, tt_local_entry,
         BATADV_TT_CLIENT_DEL, "timed out");
 }
}

/**
 * batadv_tt_local_purge() - purge inactive tt local entries
 * @bat_priv: the bat priv with all the mesh interface information
 * @timeout: parameter deciding whether a given tt local entry is considered
 *  inactive or not
 */

static void batadv_tt_local_purge(struct batadv_priv *bat_priv,
      int timeout)
{
 struct batadv_hashtable *hash = bat_priv->tt.local_hash;
 struct hlist_head *head;
 spinlock_t *list_lock; /* protects write access to the hash lists */
 u32 i;

 for (i = 0; i < hash->size; i++) {
  head = &hash->table[i];
  list_lock = &hash->list_locks[i];

  spin_lock_bh(list_lock);
  batadv_tt_local_purge_list(bat_priv, head, timeout);
  spin_unlock_bh(list_lock);
 }
}

static void batadv_tt_local_table_free(struct batadv_priv *bat_priv)
{
 struct batadv_hashtable *hash;
 spinlock_t *list_lock; /* protects write access to the hash lists */
 struct batadv_tt_common_entry *tt_common_entry;
 struct batadv_tt_local_entry *tt_local;
 struct hlist_node *node_tmp;
 struct hlist_head *head;
 u32 i;

 if (!bat_priv->tt.local_hash)
  return;

 hash = bat_priv->tt.local_hash;

 for (i = 0; i < hash->size; i++) {
  head = &hash->table[i];
  list_lock = &hash->list_locks[i];

  spin_lock_bh(list_lock);
  hlist_for_each_entry_safe(tt_common_entry, node_tmp,
       head, hash_entry) {
   hlist_del_rcu(&tt_common_entry->hash_entry);
   tt_local = container_of(tt_common_entry,
      struct batadv_tt_local_entry,
      common);

   batadv_tt_local_entry_put(tt_local);
  }
  spin_unlock_bh(list_lock);
 }

 batadv_hash_destroy(hash);

 bat_priv->tt.local_hash = NULL;
}

static int batadv_tt_global_init(struct batadv_priv *bat_priv)
{
 if (bat_priv->tt.global_hash)
  return 0;

 bat_priv->tt.global_hash = batadv_hash_new(1024);

 if (!bat_priv->tt.global_hash)
  return -ENOMEM;

 batadv_hash_set_lock_class(bat_priv->tt.global_hash,
       &batadv_tt_global_hash_lock_class_key);

 return 0;
}

static void batadv_tt_changes_list_free(struct batadv_priv *bat_priv)
{
 struct batadv_tt_change_node *entry, *safe;

 spin_lock_bh(&bat_priv->tt.changes_list_lock);

 list_for_each_entry_safe(entry, safe, &bat_priv->tt.changes_list,
     list) {
  list_del(&entry->list);
  kmem_cache_free(batadv_tt_change_cache, entry);
 }

 WRITE_ONCE(bat_priv->tt.local_changes, 0);
 spin_unlock_bh(&bat_priv->tt.changes_list_lock);
}

/**
 * batadv_tt_global_orig_entry_find() - find a TT orig_list_entry
 * @entry: the TT global entry where the orig_list_entry has to be
 *  extracted from
 * @orig_node: the originator for which the orig_list_entry has to be found
 *
 * retrieve the orig_tt_list_entry belonging to orig_node from the
 * batadv_tt_global_entry list
 *
 * Return: it with an increased refcounter, NULL if not found
 */

static struct batadv_tt_orig_list_entry *
batadv_tt_global_orig_entry_find(const struct batadv_tt_global_entry *entry,
     const struct batadv_orig_node *orig_node)
{
 struct batadv_tt_orig_list_entry *tmp_orig_entry, *orig_entry = NULL;
 const struct hlist_head *head;

 rcu_read_lock();
 head = &entry->orig_list;
 hlist_for_each_entry_rcu(tmp_orig_entry, head, list) {
  if (tmp_orig_entry->orig_node != orig_node)
   continue;
  if (!kref_get_unless_zero(&tmp_orig_entry->refcount))
   continue;

  orig_entry = tmp_orig_entry;
  break;
 }
 rcu_read_unlock();

 return orig_entry;
}

/**
 * batadv_tt_global_entry_has_orig() - check if a TT global entry is also
 *  handled by a given originator
 * @entry: the TT global entry to check
 * @orig_node: the originator to search in the list
 * @flags: a pointer to store TT flags for the given @entry received
 *  from @orig_node
 *
 * find out if an orig_node is already in the list of a tt_global_entry.
 *
 * Return: true if found, false otherwise
 */

static bool
batadv_tt_global_entry_has_orig(const struct batadv_tt_global_entry *entry,
    const struct batadv_orig_node *orig_node,
    u8 *flags)
{
 struct batadv_tt_orig_list_entry *orig_entry;
 bool found = false;

 orig_entry = batadv_tt_global_orig_entry_find(entry, orig_node);
 if (orig_entry) {
  found = true;

  if (flags)
   *flags = orig_entry->flags;

  batadv_tt_orig_list_entry_put(orig_entry);
 }

 return found;
}

/**
 * batadv_tt_global_sync_flags() - update TT sync flags
 * @tt_global: the TT global entry to update sync flags in
 *
 * Updates the sync flag bits in the tt_global flag attribute with a logical
 * OR of all sync flags from any of its TT orig entries.
 */

static void
batadv_tt_global_sync_flags(struct batadv_tt_global_entry *tt_global)
{
 struct batadv_tt_orig_list_entry *orig_entry;
 const struct hlist_head *head;
 u16 flags = BATADV_NO_FLAGS;

 rcu_read_lock();
 head = &tt_global->orig_list;
 hlist_for_each_entry_rcu(orig_entry, head, list)
  flags |= orig_entry->flags;
 rcu_read_unlock();

 flags |= tt_global->common.flags & (~BATADV_TT_SYNC_MASK);
 tt_global->common.flags = flags;
}

/**
 * batadv_tt_global_orig_entry_add() - add or update a TT orig entry
 * @tt_global: the TT global entry to add an orig entry in
 * @orig_node: the originator to add an orig entry for
 * @ttvn: translation table version number of this changeset
 * @flags: TT sync flags
 */

static void
batadv_tt_global_orig_entry_add(struct batadv_tt_global_entry *tt_global,
    struct batadv_orig_node *orig_node, int ttvn,
    u8 flags)
{
 struct batadv_tt_orig_list_entry *orig_entry;

 spin_lock_bh(&tt_global->list_lock);

 orig_entry = batadv_tt_global_orig_entry_find(tt_global, orig_node);
 if (orig_entry) {
  /* refresh the ttvn: the current value could be a bogus one that
 * was added during a "temporary client detection"
 */

  orig_entry->ttvn = ttvn;
  orig_entry->flags = flags;
  goto sync_flags;
 }

 orig_entry = kmem_cache_zalloc(batadv_tt_orig_cache, GFP_ATOMIC);
 if (!orig_entry)
  goto out;

 INIT_HLIST_NODE(&orig_entry->list);
 kref_get(&orig_node->refcount);
 batadv_tt_global_size_inc(orig_node, tt_global->common.vid);
 orig_entry->orig_node = orig_node;
 orig_entry->ttvn = ttvn;
 orig_entry->flags = flags;
 kref_init(&orig_entry->refcount);

 kref_get(&orig_entry->refcount);
 hlist_add_head_rcu(&orig_entry->list,
      &tt_global->orig_list);
 atomic_inc(&tt_global->orig_list_count);

sync_flags:
 batadv_tt_global_sync_flags(tt_global);
out:
 batadv_tt_orig_list_entry_put(orig_entry);

 spin_unlock_bh(&tt_global->list_lock);
}

/**
 * batadv_tt_global_add() - add a new TT global entry or update an existing one
 * @bat_priv: the bat priv with all the mesh interface information
 * @orig_node: the originator announcing the client
 * @tt_addr: the mac address of the non-mesh client
 * @vid: VLAN identifier
 * @flags: TT flags that have to be set for this non-mesh client
 * @ttvn: the tt version number ever announcing this non-mesh client
 *
 * Add a new TT global entry for the given originator. If the entry already
 * exists add a new reference to the given originator (a global entry can have
 * references to multiple originators) and adjust the flags attribute to reflect
 * the function argument.
 * If a TT local entry exists for this non-mesh client remove it.
 *
 * The caller must hold the orig_node refcount.
 *
 * Return: true if the new entry has been added, false otherwise
 */

static bool batadv_tt_global_add(struct batadv_priv *bat_priv,
     struct batadv_orig_node *orig_node,
     const unsigned char *tt_addr,
     unsigned short vid, u16 flags, u8 ttvn)
{
 struct batadv_tt_global_entry *tt_global_entry;
 struct batadv_tt_local_entry *tt_local_entry;
 bool ret = false;
 int hash_added;
 struct batadv_tt_common_entry *common;
 u16 local_flags;

 /* ignore global entries from backbone nodes */
 if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig, vid))
  return true;

 tt_global_entry = batadv_tt_global_hash_find(bat_priv, tt_addr, vid);
 tt_local_entry = batadv_tt_local_hash_find(bat_priv, tt_addr, vid);

 /* if the node already has a local client for this entry, it has to wait
 * for a roaming advertisement instead of manually messing up the global
 * table
 */

 if ((flags & BATADV_TT_CLIENT_TEMP) && tt_local_entry &&
     !(tt_local_entry->common.flags & BATADV_TT_CLIENT_NEW))
  goto out;

 if (!tt_global_entry) {
  tt_global_entry = kmem_cache_zalloc(batadv_tg_cache,
          GFP_ATOMIC);
  if (!tt_global_entry)
   goto out;

  common = &tt_global_entry->common;
  ether_addr_copy(common->addr, tt_addr);
  common->vid = vid;

  if (!is_multicast_ether_addr(common->addr))
   common->flags = flags & (~BATADV_TT_SYNC_MASK);

  tt_global_entry->roam_at = 0;
  /* node must store current time in case of roaming. This is
 * needed to purge this entry out on timeout (if nobody claims
 * it)
 */

  if (flags & BATADV_TT_CLIENT_ROAM)
   tt_global_entry->roam_at = jiffies;
  kref_init(&common->refcount);
  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);

  kref_get(&common->refcount);
  hash_added = batadv_hash_add(bat_priv->tt.global_hash,
          batadv_compare_tt,
          batadv_choose_tt, common,
          &common->hash_entry);

  if (unlikely(hash_added != 0)) {
   /* remove the reference for the hash */
   batadv_tt_global_entry_put(tt_global_entry);
   goto out_remove;
  }
 } else {
  common = &tt_global_entry->common;
  /* If there is already a global entry, we can use this one for
 * our processing.
 * But if we are trying to add a temporary client then here are
 * two options at this point:
 * 1) the global client is not a temporary client: the global
 *    client has to be left as it is, temporary information
 *    should never override any already known client state
 * 2) the global client is a temporary client: purge the
 *    originator list and add the new one orig_entry
 */

  if (flags & BATADV_TT_CLIENT_TEMP) {
   if (!(common->flags & BATADV_TT_CLIENT_TEMP))
    goto out;
   if (batadv_tt_global_entry_has_orig(tt_global_entry,
           orig_node, NULL))
    goto out_remove;
   batadv_tt_global_del_orig_list(tt_global_entry);
   goto add_orig_entry;
  }

  /* if the client was temporary added before receiving the first
 * OGM announcing it, we have to clear the TEMP flag. Also,
 * remove the previous temporary orig node and re-add it
 * if required. If the orig entry changed, the new one which
 * is a non-temporary entry is preferred.
 */

  if (common->flags & BATADV_TT_CLIENT_TEMP) {
   batadv_tt_global_del_orig_list(tt_global_entry);
   common->flags &= ~BATADV_TT_CLIENT_TEMP;
  }

  /* the change can carry possible "attribute" flags like the
 * TT_CLIENT_TEMP, therefore they have to be copied in the
 * client entry
 */

  if (!is_multicast_ether_addr(common->addr))
   common->flags |= flags & (~BATADV_TT_SYNC_MASK);

  /* If there is the BATADV_TT_CLIENT_ROAM flag set, there is only
 * one originator left in the list and we previously received a
 * delete + roaming change for this originator.
 *
 * We should first delete the old originator before adding the
 * new one.
 */

  if (common->flags & BATADV_TT_CLIENT_ROAM) {
   batadv_tt_global_del_orig_list(tt_global_entry);
   common->flags &= ~BATADV_TT_CLIENT_ROAM;
   tt_global_entry->roam_at = 0;
  }
 }
add_orig_entry:
 /* add the new orig_entry (if needed) or update it */
 batadv_tt_global_orig_entry_add(tt_global_entry, orig_node, ttvn,
     flags & BATADV_TT_SYNC_MASK);

 batadv_dbg(BATADV_DBG_TT, bat_priv,
     "Creating new global tt entry: %pM (vid: %d, via %pM)\n",
     common->addr, batadv_print_vid(common->vid),
     orig_node->orig);
 ret = true;

out_remove:
 /* Do not remove multicast addresses from the local hash on
 * global additions
 */

 if (is_multicast_ether_addr(tt_addr))
  goto out;

 /* remove address from local hash if present */
 local_flags = batadv_tt_local_remove(bat_priv, tt_addr, vid,
          "global tt received",
          flags & BATADV_TT_CLIENT_ROAM);
 tt_global_entry->common.flags |= local_flags & BATADV_TT_CLIENT_WIFI;

 if (!(flags & BATADV_TT_CLIENT_ROAM))
  /* this is a normal global add. Therefore the client is not in a
 * roaming state anymore.
 */

  tt_global_entry->common.flags &= ~BATADV_TT_CLIENT_ROAM;

out:
 batadv_tt_global_entry_put(tt_global_entry);
 batadv_tt_local_entry_put(tt_local_entry);
 return ret;
}

/**
 * batadv_transtable_best_orig() - Get best originator list entry from tt entry
 * @bat_priv: the bat priv with all the mesh interface information
 * @tt_global_entry: global translation table entry to be analyzed
 *
 * This function assumes the caller holds rcu_read_lock().
 * Return: best originator list entry or NULL on errors.
 */

static struct batadv_tt_orig_list_entry *
batadv_transtable_best_orig(struct batadv_priv *bat_priv,
       struct batadv_tt_global_entry *tt_global_entry)
{
 struct batadv_neigh_node *router, *best_router = NULL;
 struct batadv_algo_ops *bao = bat_priv->algo_ops;
 struct hlist_head *head;
 struct batadv_tt_orig_list_entry *orig_entry, *best_entry = NULL;

 head = &tt_global_entry->orig_list;
 hlist_for_each_entry_rcu(orig_entry, head, list) {
  router = batadv_orig_router_get(orig_entry->orig_node,
      BATADV_IF_DEFAULT);
  if (!router)
   continue;

  if (best_router &&
      bao->neigh.cmp(router, BATADV_IF_DEFAULT, best_router,
       BATADV_IF_DEFAULT) <= 0) {
   batadv_neigh_node_put(router);
   continue;
  }

  /* release the refcount for the "old" best */
  batadv_neigh_node_put(best_router);

  best_entry = orig_entry;
  best_router = router;
 }

 batadv_neigh_node_put(best_router);

 return best_entry;
}

/**
 * batadv_tt_global_dump_subentry() - Dump all TT local entries into a message
 * @msg: Netlink message to dump into
 * @portid: Port making netlink request
 * @seq: Sequence number of netlink message
 * @common: tt local & tt global common data
 * @orig: Originator node announcing a non-mesh client
 * @best: Is the best originator for the TT entry
 *
 * Return: Error code, or 0 on success
 */

static int
batadv_tt_global_dump_subentry(struct sk_buff *msg, u32 portid, u32 seq,
          struct batadv_tt_common_entry *common,
          struct batadv_tt_orig_list_entry *orig,
          bool best)
{
 u16 flags = (common->flags & (~BATADV_TT_SYNC_MASK)) | orig->flags;
 void *hdr;
 struct batadv_orig_node_vlan *vlan;
 u8 last_ttvn;
 u32 crc;

 vlan = batadv_orig_node_vlan_get(orig->orig_node,
      common->vid);
 if (!vlan)
  return 0;

 crc = vlan->tt.crc;

 batadv_orig_node_vlan_put(vlan);

 hdr = genlmsg_put(msg, portid, seq, &batadv_netlink_family,
     NLM_F_MULTI,
     BATADV_CMD_GET_TRANSTABLE_GLOBAL);
 if (!hdr)
  return -ENOBUFS;

 last_ttvn = atomic_read(&orig->orig_node->last_ttvn);

 if (nla_put(msg, BATADV_ATTR_TT_ADDRESS, ETH_ALEN, common->addr) ||
     nla_put(msg, BATADV_ATTR_ORIG_ADDRESS, ETH_ALEN,
      orig->orig_node->orig) ||
     nla_put_u8(msg, BATADV_ATTR_TT_TTVN, orig->ttvn) ||
     nla_put_u8(msg, BATADV_ATTR_TT_LAST_TTVN, last_ttvn) ||
     nla_put_u32(msg, BATADV_ATTR_TT_CRC32, crc) ||
     nla_put_u16(msg, BATADV_ATTR_TT_VID, common->vid) ||
     nla_put_u32(msg, BATADV_ATTR_TT_FLAGS, flags))
  goto nla_put_failure;

 if (best && nla_put_flag(msg, BATADV_ATTR_FLAG_BEST))
  goto nla_put_failure;

 genlmsg_end(msg, hdr);
 return 0;

 nla_put_failure:
 genlmsg_cancel(msg, hdr);
 return -EMSGSIZE;
}

/**
 * batadv_tt_global_dump_entry() - Dump one TT global entry into a message
 * @msg: Netlink message to dump into
 * @portid: Port making netlink request
 * @seq: Sequence number of netlink message
 * @bat_priv: The bat priv with all the mesh interface information
 * @common: tt local & tt global common data
 * @sub_s: Number of entries to skip
 *
 * This function assumes the caller holds rcu_read_lock().
 *
 * Return: Error code, or 0 on success
 */

static int
batadv_tt_global_dump_entry(struct sk_buff *msg, u32 portid, u32 seq,
       struct batadv_priv *bat_priv,
       struct batadv_tt_common_entry *common, int *sub_s)
{
 struct batadv_tt_orig_list_entry *orig_entry, *best_entry;
 struct batadv_tt_global_entry *global;
 struct hlist_head *head;
 int sub = 0;
 bool best;

 global = container_of(common, struct batadv_tt_global_entry, common);
 best_entry = batadv_transtable_best_orig(bat_priv, global);
 head = &global->orig_list;

 hlist_for_each_entry_rcu(orig_entry, head, list) {
  if (sub++ < *sub_s)
   continue;

  best = (orig_entry == best_entry);

  if (batadv_tt_global_dump_subentry(msg, portid, seq, common,
         orig_entry, best)) {
   *sub_s = sub - 1;
   return -EMSGSIZE;
  }
 }

 *sub_s = 0;
 return 0;
}

/**
 * batadv_tt_global_dump_bucket() - Dump one TT local bucket into a message
 * @msg: Netlink message to dump into
 * @portid: Port making netlink request
 * @seq: Sequence number of netlink message
 * @bat_priv: The bat priv with all the mesh interface information
 * @head: Pointer to the list containing the global tt entries
 * @idx_s: Number of entries to skip
 * @sub: Number of entries to skip
 *
 * Return: Error code, or 0 on success
 */

static int
batadv_tt_global_dump_bucket(struct sk_buff *msg, u32 portid, u32 seq,
        struct batadv_priv *bat_priv,
        struct hlist_head *head, int *idx_s, int *sub)
{
 struct batadv_tt_common_entry *common;
 int idx = 0;

 rcu_read_lock();
 hlist_for_each_entry_rcu(common, head, hash_entry) {
  if (idx++ < *idx_s)
   continue;

  if (batadv_tt_global_dump_entry(msg, portid, seq, bat_priv,
      common, sub)) {
   rcu_read_unlock();
   *idx_s = idx - 1;
   return -EMSGSIZE;
  }
 }
 rcu_read_unlock();

 *idx_s = 0;
 *sub = 0;
 return 0;
}

/**
 * batadv_tt_global_dump() -  Dump TT global entries into a message
 * @msg: Netlink message to dump into
 * @cb: Parameters from query
 *
 * Return: Error code, or length of message on success
 */

int batadv_tt_global_dump(struct sk_buff *msg, struct netlink_callback *cb)
{
 struct net_device *mesh_iface;
 struct batadv_priv *bat_priv;
 struct batadv_hard_iface *primary_if = NULL;
 struct batadv_hashtable *hash;
 struct hlist_head *head;
 int ret;
 int bucket = cb->args[0];
 int idx = cb->args[1];
 int sub = cb->args[2];
 int portid = NETLINK_CB(cb->skb).portid;

 mesh_iface = batadv_netlink_get_meshif(cb);
 if (IS_ERR(mesh_iface))
  return PTR_ERR(mesh_iface);

 bat_priv = netdev_priv(mesh_iface);

 primary_if = batadv_primary_if_get_selected(bat_priv);
 if (!primary_if || primary_if->if_status != BATADV_IF_ACTIVE) {
  ret = -ENOENT;
  goto out;
 }

 hash = bat_priv->tt.global_hash;

 while (bucket < hash->size) {
  head = &hash->table[bucket];

  if (batadv_tt_global_dump_bucket(msg, portid,
       cb->nlh->nlmsg_seq, bat_priv,
       head, &idx, &sub))
   break;

  bucket++;
 }

 ret = msg->len;

 out:
 batadv_hardif_put(primary_if);
 dev_put(mesh_iface);

 cb->args[0] = bucket;
 cb->args[1] = idx;
 cb->args[2] = sub;

 return ret;
}

/**
 * _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.
 *
 * Caller must hold tt_global_entry->list_lock and ensure orig_entry->list is
 * part of a list.
 */

static void
_batadv_tt_global_del_orig_entry(struct batadv_tt_global_entry *tt_global_entry,
     struct batadv_tt_orig_list_entry *orig_entry)
{
 lockdep_assert_held(&tt_global_entry->list_lock);

 batadv_tt_global_size_dec(orig_entry->orig_node,
      tt_global_entry->common.vid);
 atomic_dec(&tt_global_entry->orig_list_count);
 /* requires holding tt_global_entry->list_lock and orig_entry->list
 * being part of a list
 */

 hlist_del_rcu(&orig_entry->list);
 batadv_tt_orig_list_entry_put(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)
{
 struct hlist_head *head;
 struct hlist_node *safe;
 struct batadv_tt_orig_list_entry *orig_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)
  _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 mesh 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_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;
 struct batadv_tt_orig_list_entry *orig_entry;
 unsigned short vid;

 spin_lock_bh(&tt_global_entry->list_lock);
 head = &tt_global_entry->orig_list;
 hlist_for_each_entry_safe(orig_entry, safe, head, list) {
  if (orig_entry->orig_node == orig_node) {
   vid = tt_global_entry->common.vid;
   batadv_dbg(BATADV_DBG_TT, bat_priv,
       "Deleting %pM from global tt entry %pM (vid: %d): %s\n",
       orig_node->orig,
       tt_global_entry->common.addr,
       batadv_print_vid(vid), message);
   _batadv_tt_global_del_orig_entry(tt_global_entry,
        orig_entry);
  }
 }
 spin_unlock_bh(&tt_global_entry->list_lock);
}

/* If the client is to be deleted, we check if it is the last origantor entry
 * within tt_global entry. If yes, we set the BATADV_TT_CLIENT_ROAM flag and the
 * timer, otherwise we simply remove the originator scheduled for deletion.
 */

static void
batadv_tt_global_del_roaming(struct batadv_priv *bat_priv,
        struct batadv_tt_global_entry *tt_global_entry,
        struct batadv_orig_node *orig_node,
        const char *message)
{
 bool last_entry = true;
 struct hlist_head *head;
 struct batadv_tt_orig_list_entry *orig_entry;

 /* no local entry exists, case 1:
 * Check if this is the last one or if other entries exist.
 */


 rcu_read_lock();
 head = &tt_global_entry->orig_list;
 hlist_for_each_entry_rcu(orig_entry, head, list) {
  if (orig_entry->orig_node != orig_node) {
   last_entry = false;
   break;
  }
 }
 rcu_read_unlock();

 if (last_entry) {
  /* its the last one, mark for roaming. */
  tt_global_entry->common.flags |= BATADV_TT_CLIENT_ROAM;
  tt_global_entry->roam_at = jiffies;
 } else {
  /* there is another entry, we can simply delete this
 * one and can still use the other one.
 */

  batadv_tt_global_del_orig_node(bat_priv, tt_global_entry,
            orig_node, message);
 }
}

/**
 * batadv_tt_global_del() - remove a client from the global table
 * @bat_priv: the bat priv with all the mesh interface information
 * @orig_node: an originator serving this client
 * @addr: the mac address of the client
 * @vid: VLAN identifier
 * @message: a message explaining the reason for deleting the client to print
 *  for debugging purpose
 * @roaming: true if the deletion has been triggered by a roaming event
 */

static void batadv_tt_global_del(struct batadv_priv *bat_priv,
     struct batadv_orig_node *orig_node,
     const unsigned char *addr, unsigned short vid,
     const char *message, bool roaming)
{
 struct batadv_tt_global_entry *tt_global_entry;
 struct batadv_tt_local_entry *local_entry = NULL;

 tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid);
 if (!tt_global_entry)
  goto out;

 if (!roaming) {
  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,
           message);

  goto out;
 }

 /* if we are deleting a global entry due to a roam
 * event, there are two possibilities:
 * 1) the client roamed from node A to node B => if there
 *    is only one originator left for this client, we mark
 *    it with BATADV_TT_CLIENT_ROAM, we start a timer and we
 *    wait for node B to claim it. In case of timeout
 *    the entry is purged.
 *
 *    If there are other originators left, we directly delete
 *    the originator.
 * 2) the client roamed to us => we can directly delete
 *    the global entry, since it is useless now.
 */

 local_entry = batadv_tt_local_hash_find(bat_priv,
      tt_global_entry->common.addr,
      vid);
 if (local_entry) {
  /* local entry exists, case 2: client roamed to us. */
  batadv_tt_global_del_orig_list(tt_global_entry);
  batadv_tt_global_free(bat_priv, tt_global_entry, message);
 } else {
  /* no local entry exists, case 1: check for roaming */
  batadv_tt_global_del_roaming(bat_priv, tt_global_entry,
          orig_node, message);
 }

out:
 batadv_tt_global_entry_put(tt_global_entry);
 batadv_tt_local_entry_put(local_entry);
}

/**
 * batadv_tt_global_del_orig() - remove all the TT global entries belonging to
 *  the given originator matching the provided vid
 * @bat_priv: the bat priv with all the mesh interface information
 * @orig_node: the originator owning the entries to remove
 * @match_vid: the VLAN identifier to match. If negative all the entries will be
 *  removed
 * @message: debug message to print as "reason"
 */

void batadv_tt_global_del_orig(struct batadv_priv *bat_priv,
          struct batadv_orig_node *orig_node,
          s32 match_vid,
          const char *message)
{
 struct batadv_tt_global_entry *tt_global;
 struct batadv_tt_common_entry *tt_common_entry;
 u32 i;
 struct batadv_hashtable *hash = bat_priv->tt.global_hash;
 struct hlist_node *safe;
 struct hlist_head *head;
 spinlock_t *list_lock; /* protects write access to the hash lists */
 unsigned short vid;

 if (!hash)
  return;

 for (i = 0; i < hash->size; i++) {
  head = &hash->table[i];
  list_lock = &hash->list_locks[i];

  spin_lock_bh(list_lock);
  hlist_for_each_entry_safe(tt_common_entry, safe,
       head, hash_entry) {
   /* remove only matching entries */
   if (match_vid >= 0 && tt_common_entry->vid != match_vid)
    continue;

   tt_global = container_of(tt_common_entry,
       struct batadv_tt_global_entry,
       common);

   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;
    batadv_dbg(BATADV_DBG_TT, bat_priv,
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=91 G=93

¤ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.