linux/net/batman-adv/translation-table.c
Antonio Quartulli cfd4f75701 batman-adv: make the backbone gw check VLAN specific
The backbone gw check has to be VLAN specific so that code
using it can specify VID where the check has to be done.

In the TT code, the check has been moved into the
tt_global_add() function so that it can be performed on a
per-entry basis instead of ignoring all the TT data received
from another backbone node. Only TT global entries belonging
to the VLAN where the backbone node is connected to are
skipped.
All the other spots where the TT code was checking whether a
node is a backbone have been removed.

Moreover, batadv_bla_is_backbone_gw_orig() now returns bool
since it used to return only 1 or 0.

Cc: Simon Wunderlich <siwu@hrz.tu-chemnitz.de>
Signed-off-by: Antonio Quartulli <antonio@open-mesh.com>
Signed-off-by: Marek Lindner <lindner_marek@yahoo.de>
2013-10-19 23:25:38 +02:00

3389 lines
100 KiB
C

/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors:
*
* Marek Lindner, Simon Wunderlich, Antonio Quartulli
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
#include "main.h"
#include "translation-table.h"
#include "soft-interface.h"
#include "hard-interface.h"
#include "send.h"
#include "hash.h"
#include "originator.h"
#include "routing.h"
#include "bridge_loop_avoidance.h"
#include <linux/crc32c.h>
/* 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, uint8_t *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);
/* returns 1 if they are the same mac addr */
static int batadv_compare_tt(const struct hlist_node *node, const void *data2)
{
const void *data1 = container_of(node, struct batadv_tt_common_entry,
hash_entry);
return (memcmp(data1, data2, ETH_ALEN) == 0 ? 1 : 0);
}
/**
* 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
*
* Returns the hash index where the object represented by 'data' should be
* stored at.
*/
static inline uint32_t batadv_choose_tt(const void *data, uint32_t size)
{
struct batadv_tt_common_entry *tt;
uint32_t hash = 0;
tt = (struct batadv_tt_common_entry *)data;
hash = batadv_hash_bytes(hash, &tt->addr, ETH_ALEN);
hash = batadv_hash_bytes(hash, &tt->vid, sizeof(tt->vid));
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
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
*
* Returns 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 uint8_t *addr,
unsigned short vid)
{
struct hlist_head *head;
struct batadv_tt_common_entry to_search, *tt, *tt_tmp = NULL;
uint32_t index;
if (!hash)
return NULL;
memcpy(to_search.addr, addr, ETH_ALEN);
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 (!atomic_inc_not_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 soft interface information
* @addr: the mac address of the client to look for
* @vid: VLAN identifier
*
* Returns 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 uint8_t *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 soft interface information
* @addr: the mac address of the client to look for
* @vid: VLAN identifier
*
* Returns a pointer to the corresponding tt_global_entry struct if the client
* is found, NULL otherwise.
*/
static struct batadv_tt_global_entry *
batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const uint8_t *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;
}
static void
batadv_tt_local_entry_free_ref(struct batadv_tt_local_entry *tt_local_entry)
{
if (atomic_dec_and_test(&tt_local_entry->common.refcount))
kfree_rcu(tt_local_entry, common.rcu);
}
/**
* batadv_tt_global_entry_free_ref - decrement the refcounter for a
* tt_global_entry and possibly free it
* @tt_global_entry: the object to free
*/
static void
batadv_tt_global_entry_free_ref(struct batadv_tt_global_entry *tt_global_entry)
{
if (atomic_dec_and_test(&tt_global_entry->common.refcount)) {
batadv_tt_global_del_orig_list(tt_global_entry);
kfree_rcu(tt_global_entry, common.rcu);
}
}
static void batadv_tt_orig_list_entry_free_rcu(struct rcu_head *rcu)
{
struct batadv_tt_orig_list_entry *orig_entry;
orig_entry = container_of(rcu, struct batadv_tt_orig_list_entry, rcu);
/* We are in an rcu callback here, therefore we cannot use
* batadv_orig_node_free_ref() and its call_rcu():
* An rcu_barrier() wouldn't wait for that to finish
*/
batadv_orig_node_free_ref_now(orig_entry->orig_node);
kfree(orig_entry);
}
/**
* 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 soft 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_softif_vlan *vlan;
vlan = batadv_softif_vlan_get(bat_priv, vid);
if (!vlan)
return;
atomic_add(v, &vlan->tt.num_entries);
batadv_softif_vlan_free_ref(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 soft 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 soft 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 local table
* identified by vid
* @bat_priv: the bat priv with all the soft interface information
* @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);
list_del_rcu(&vlan->list);
spin_unlock_bh(&orig_node->vlan_list_lock);
batadv_orig_node_vlan_free_ref(vlan);
}
batadv_orig_node_vlan_free_ref(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);
}
static void
batadv_tt_orig_list_entry_free_ref(struct batadv_tt_orig_list_entry *orig_entry)
{
if (!atomic_dec_and_test(&orig_entry->refcount))
return;
call_rcu(&orig_entry->rcu, batadv_tt_orig_list_entry_free_rcu);
}
/**
* batadv_tt_local_event - store a local TT event (ADD/DEL)
* @bat_priv: the bat priv with all the soft 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,
uint8_t event_flags)
{
struct batadv_tt_change_node *tt_change_node, *entry, *safe;
struct batadv_tt_common_entry *common = &tt_local_entry->common;
uint8_t flags = common->flags | event_flags;
bool event_removed = false;
bool del_op_requested, del_op_entry;
tt_change_node = kmalloc(sizeof(*tt_change_node), GFP_ATOMIC);
if (!tt_change_node)
return;
tt_change_node->change.flags = flags;
tt_change_node->change.reserved = 0;
memcpy(tt_change_node->change.addr, common->addr, ETH_ALEN);
tt_change_node->change.vid = htons(common->vid);
del_op_requested = flags & BATADV_TT_CLIENT_DEL;
/* check for ADD+DEL or DEL+ADD events */
spin_lock_bh(&bat_priv->tt.changes_list_lock);
list_for_each_entry_safe(entry, safe, &bat_priv->tt.changes_list,
list) {
if (!batadv_compare_eth(entry->change.addr, common->addr))
continue;
/* 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
*/
del_op_entry = entry->change.flags & BATADV_TT_CLIENT_DEL;
if (!del_op_requested && del_op_entry)
goto del;
if (del_op_requested && !del_op_entry)
goto del;
continue;
del:
list_del(&entry->list);
kfree(entry);
kfree(tt_change_node);
event_removed = true;
goto unlock;
}
/* track the change in the OGMinterval list */
list_add_tail(&tt_change_node->list, &bat_priv->tt.changes_list);
unlock:
spin_unlock_bh(&bat_priv->tt.changes_list_lock);
if (event_removed)
atomic_dec(&bat_priv->tt.local_changes);
else
atomic_inc(&bat_priv->tt.local_changes);
}
/**
* batadv_tt_len - compute length in bytes of given number of tt changes
* @changes_num: number of tt changes
*
* Returns 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
*
* Returns the number of entries.
*/
static uint16_t batadv_tt_entries(uint16_t tt_len)
{
return tt_len / batadv_tt_len(1);
}
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)
{
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);
batadv_hash_remove(bat_priv->tt.global_hash, batadv_compare_tt,
batadv_choose_tt, &tt_global->common);
batadv_tt_global_entry_free_ref(tt_global);
}
/**
* batadv_tt_local_add - add a new client to the local table or update an
* existing client
* @soft_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)
*/
void batadv_tt_local_add(struct net_device *soft_iface, const uint8_t *addr,
unsigned short vid, int ifindex)
{
struct batadv_priv *bat_priv = netdev_priv(soft_iface);
struct batadv_tt_local_entry *tt_local;
struct batadv_tt_global_entry *tt_global;
struct hlist_head *head;
struct batadv_tt_orig_list_entry *orig_entry;
int hash_added;
bool roamed_back = false;
tt_local = batadv_tt_local_hash_find(bat_priv, addr, vid);
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;
}
tt_local = kmalloc(sizeof(*tt_local), GFP_ATOMIC);
if (!tt_local)
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),
(uint8_t)atomic_read(&bat_priv->tt.vn));
memcpy(tt_local->common.addr, addr, ETH_ALEN);
/* 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_iface(ifindex))
tt_local->common.flags |= BATADV_TT_CLIENT_WIFI;
atomic_set(&tt_local->common.refcount, 2);
tt_local->last_seen = jiffies;
tt_local->common.added_at = tt_local->last_seen;
/* the batman interface mac address should never be purged */
if (batadv_compare_eth(addr, soft_iface->dev_addr))
tt_local->common.flags |= BATADV_TT_CLIENT_NOPURGE;
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_free_ref(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");
tt_global = NULL;
} 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;
}
}
out:
if (tt_local)
batadv_tt_local_entry_free_ref(tt_local);
if (tt_global)
batadv_tt_global_entry_free_ref(tt_global);
}
/**
* 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 by 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 uint16_t
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,
int32_t *tt_len)
{
uint16_t num_vlan = 0, num_entries = 0, change_offset, tvlv_len;
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_orig_node_vlan *vlan;
uint8_t *tt_change_ptr;
rcu_read_lock();
list_for_each_entry_rcu(vlan, &orig_node->vlan_list, list) {
num_vlan++;
num_entries += atomic_read(&vlan->tt.num_entries);
}
change_offset = sizeof(**tt_data);
change_offset += num_vlan * sizeof(*tt_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 = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
list_for_each_entry_rcu(vlan, &orig_node->vlan_list, list) {
tt_vlan->vid = htons(vlan->vid);
tt_vlan->crc = htonl(vlan->tt.crc);
tt_vlan++;
}
tt_change_ptr = (uint8_t *)*tt_data + change_offset;
*tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
out:
rcu_read_unlock();
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 soft 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 uint16_t
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,
int32_t *tt_len)
{
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_softif_vlan *vlan;
uint16_t num_vlan = 0, num_entries = 0, tvlv_len;
uint8_t *tt_change_ptr;
int change_offset;
rcu_read_lock();
hlist_for_each_entry_rcu(vlan, &bat_priv->softif_vlan_list, list) {
num_vlan++;
num_entries += atomic_read(&vlan->tt.num_entries);
}
change_offset = sizeof(**tt_data);
change_offset += num_vlan * sizeof(*tt_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) {
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 = (struct batadv_tvlv_tt_vlan_data *)(*tt_data + 1);
hlist_for_each_entry_rcu(vlan, &bat_priv->softif_vlan_list, list) {
tt_vlan->vid = htons(vlan->vid);
tt_vlan->crc = htonl(vlan->tt.crc);
tt_vlan++;
}
tt_change_ptr = (uint8_t *)*tt_data + change_offset;
*tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
out:
rcu_read_unlock();
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 soft 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, tt_diff_entries_count = 0;
uint16_t tvlv_len;
tt_diff_entries_num = atomic_read(&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 which will be fragmented
*/
if (tt_diff_len > bat_priv->soft_iface->mtu)
tt_diff_len = 0;
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 (tt_diff_len == 0)
goto container_register;
spin_lock_bh(&bat_priv->tt.changes_list_lock);
atomic_set(&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);
kfree(entry);
}
spin_unlock_bh(&bat_priv->tt.changes_list_lock);
/* 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) {
/* 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);
container_register:
batadv_tvlv_container_register(bat_priv, BATADV_TVLV_TT, 1, tt_data,
tvlv_len);
kfree(tt_data);
}
int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset)
{
struct net_device *net_dev = (struct net_device *)seq->private;
struct batadv_priv *bat_priv = netdev_priv(net_dev);
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
struct batadv_tt_common_entry *tt_common_entry;
struct batadv_tt_local_entry *tt_local;
struct batadv_hard_iface *primary_if;
struct batadv_softif_vlan *vlan;
struct hlist_head *head;
unsigned short vid;
uint32_t i;
int last_seen_secs;
int last_seen_msecs;
unsigned long last_seen_jiffies;
bool no_purge;
uint16_t np_flag = BATADV_TT_CLIENT_NOPURGE;
primary_if = batadv_seq_print_text_primary_if_get(seq);
if (!primary_if)
goto out;
seq_printf(seq,
"Locally retrieved addresses (from %s) announced via TT (TTVN: %u):\n",
net_dev->name, (uint8_t)atomic_read(&bat_priv->tt.vn));
seq_printf(seq, " %-13s %s %-7s %-9s (%-10s)\n", "Client", "VID",
"Flags", "Last seen", "CRC");
for (i = 0; i < hash->size; i++) {
head = &hash->table[i];
rcu_read_lock();
hlist_for_each_entry_rcu(tt_common_entry,
head, hash_entry) {
tt_local = container_of(tt_common_entry,
struct batadv_tt_local_entry,
common);
vid = tt_common_entry->vid;
last_seen_jiffies = jiffies - tt_local->last_seen;
last_seen_msecs = jiffies_to_msecs(last_seen_jiffies);
last_seen_secs = last_seen_msecs / 1000;
last_seen_msecs = last_seen_msecs % 1000;
no_purge = tt_common_entry->flags & np_flag;
vlan = batadv_softif_vlan_get(bat_priv, vid);
if (!vlan) {
seq_printf(seq, "Cannot retrieve VLAN %d\n",
BATADV_PRINT_VID(vid));
continue;
}
seq_printf(seq,
" * %pM %4i [%c%c%c%c%c] %3u.%03u (%#.8x)\n",
tt_common_entry->addr,
BATADV_PRINT_VID(tt_common_entry->vid),
(tt_common_entry->flags &
BATADV_TT_CLIENT_ROAM ? 'R' : '.'),
no_purge ? 'P' : '.',
(tt_common_entry->flags &
BATADV_TT_CLIENT_NEW ? 'N' : '.'),
(tt_common_entry->flags &
BATADV_TT_CLIENT_PENDING ? 'X' : '.'),
(tt_common_entry->flags &
BATADV_TT_CLIENT_WIFI ? 'W' : '.'),
no_purge ? 0 : last_seen_secs,
no_purge ? 0 : last_seen_msecs,
vlan->tt.crc);
batadv_softif_vlan_free_ref(vlan);
}
rcu_read_unlock();
}
out:
if (primary_if)
batadv_hardif_free_ref(primary_if);
return 0;
}
static void
batadv_tt_local_set_pending(struct batadv_priv *bat_priv,
struct batadv_tt_local_entry *tt_local_entry,
uint16_t 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 soft 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
*
* Returns the flags assigned to the local entry before being deleted
*/
uint16_t batadv_tt_local_remove(struct batadv_priv *bat_priv,
const uint8_t *addr, unsigned short vid,
const char *message, bool roaming)
{
struct batadv_tt_local_entry *tt_local_entry;
uint16_t flags, curr_flags = BATADV_NO_FLAGS;
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);
hlist_del_rcu(&tt_local_entry->common.hash_entry);
batadv_tt_local_entry_free_ref(tt_local_entry);
out:
if (tt_local_entry)
batadv_tt_local_entry_free_ref(tt_local_entry);
return curr_flags;
}
static void batadv_tt_local_purge_list(struct batadv_priv *bat_priv,
struct hlist_head *head)
{
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,
BATADV_TT_LOCAL_TIMEOUT))
continue;
batadv_tt_local_set_pending(bat_priv, tt_local_entry,
BATADV_TT_CLIENT_DEL, "timed out");
}
}
static void batadv_tt_local_purge(struct batadv_priv *bat_priv)
{
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
struct hlist_head *head;
spinlock_t *list_lock; /* protects write access to the hash lists */
uint32_t 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);
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;
uint32_t 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_free_ref(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);
kfree(entry);
}
atomic_set(&bat_priv->tt.local_changes, 0);
spin_unlock_bh(&bat_priv->tt.changes_list_lock);
}
/* retrieves the orig_tt_list_entry belonging to orig_node from the
* batadv_tt_global_entry list
*
* returns 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 (!atomic_inc_not_zero(&tmp_orig_entry->refcount))
continue;
orig_entry = tmp_orig_entry;
break;
}
rcu_read_unlock();
return orig_entry;
}
/* find out if an orig_node is already in the list of a tt_global_entry.
* returns 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)
{
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;
batadv_tt_orig_list_entry_free_ref(orig_entry);
}
return found;
}
static void
batadv_tt_global_orig_entry_add(struct batadv_tt_global_entry *tt_global,
struct batadv_orig_node *orig_node, int ttvn)
{
struct batadv_tt_orig_list_entry *orig_entry;
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;
goto out;
}
orig_entry = kzalloc(sizeof(*orig_entry), GFP_ATOMIC);
if (!orig_entry)
goto out;
INIT_HLIST_NODE(&orig_entry->list);
atomic_inc(&orig_node->refcount);
batadv_tt_global_size_inc(orig_node, tt_global->common.vid);
orig_entry->orig_node = orig_node;
orig_entry->ttvn = ttvn;
atomic_set(&orig_entry->refcount, 2);
spin_lock_bh(&tt_global->list_lock);
hlist_add_head_rcu(&orig_entry->list,
&tt_global->orig_list);
spin_unlock_bh(&tt_global->list_lock);
out:
if (orig_entry)
batadv_tt_orig_list_entry_free_ref(orig_entry);
}
/**
* batadv_tt_global_add - add a new TT global entry or update an existing one
* @bat_priv: the bat priv with all the soft 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 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, uint16_t flags,
uint8_t 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;
uint16_t 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 = kzalloc(sizeof(*tt_global_entry), GFP_ATOMIC);
if (!tt_global_entry)
goto out;
common = &tt_global_entry->common;
memcpy(common->addr, tt_addr, ETH_ALEN);
common->vid = vid;
common->flags = flags;
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;
atomic_set(&common->refcount, 2);
common->added_at = jiffies;
INIT_HLIST_HEAD(&tt_global_entry->orig_list);
spin_lock_init(&tt_global_entry->list_lock);
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_free_ref(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))
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
*/
common->flags &= ~BATADV_TT_CLIENT_TEMP;
/* the change can carry possible "attribute" flags like the
* TT_CLIENT_WIFI, therefore they have to be copied in the
* client entry
*/
tt_global_entry->common.flags |= flags;
/* 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);
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:
/* 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:
if (tt_global_entry)
batadv_tt_global_entry_free_ref(tt_global_entry);
if (tt_local_entry)
batadv_tt_local_entry_free_ref(tt_local_entry);
return ret;
}
/* batadv_transtable_best_orig - Get best originator list entry from tt entry
* @tt_global_entry: global translation table entry to be analyzed
*
* This functon assumes the caller holds rcu_read_lock().
* Returns best originator list entry or NULL on errors.
*/
static struct batadv_tt_orig_list_entry *
batadv_transtable_best_orig(struct batadv_tt_global_entry *tt_global_entry)
{
struct batadv_neigh_node *router = NULL;
struct hlist_head *head;
struct batadv_tt_orig_list_entry *orig_entry, *best_entry = NULL;
int best_tq = 0;
head = &tt_global_entry->orig_list;
hlist_for_each_entry_rcu(orig_entry, head, list) {
router = batadv_orig_node_get_router(orig_entry->orig_node);
if (!router)
continue;
if (router->tq_avg > best_tq) {
best_entry = orig_entry;
best_tq = router->tq_avg;
}
batadv_neigh_node_free_ref(router);
}
return best_entry;
}
/* batadv_tt_global_print_entry - print all orig nodes who announce the address
* for this global entry
* @tt_global_entry: global translation table entry to be printed
* @seq: debugfs table seq_file struct
*
* This functon assumes the caller holds rcu_read_lock().
*/
static void
batadv_tt_global_print_entry(struct batadv_tt_global_entry *tt_global_entry,
struct seq_file *seq)
{
struct batadv_tt_orig_list_entry *orig_entry, *best_entry;
struct batadv_tt_common_entry *tt_common_entry;
struct batadv_orig_node_vlan *vlan;
struct hlist_head *head;
uint8_t last_ttvn;
uint16_t flags;
tt_common_entry = &tt_global_entry->common;
flags = tt_common_entry->flags;
best_entry = batadv_transtable_best_orig(tt_global_entry);
if (best_entry) {
vlan = batadv_orig_node_vlan_get(best_entry->orig_node,
tt_common_entry->vid);
if (!vlan) {
seq_printf(seq,
" * Cannot retrieve VLAN %d for originator %pM\n",
BATADV_PRINT_VID(tt_common_entry->vid),
best_entry->orig_node->orig);
goto print_list;
}
last_ttvn = atomic_read(&best_entry->orig_node->last_ttvn);
seq_printf(seq,
" %c %pM %4i (%3u) via %pM (%3u) (%#.8x) [%c%c%c]\n",
'*', tt_global_entry->common.addr,
BATADV_PRINT_VID(tt_global_entry->common.vid),
best_entry->ttvn, best_entry->orig_node->orig,
last_ttvn, vlan->tt.crc,
(flags & BATADV_TT_CLIENT_ROAM ? 'R' : '.'),
(flags & BATADV_TT_CLIENT_WIFI ? 'W' : '.'),
(flags & BATADV_TT_CLIENT_TEMP ? 'T' : '.'));
batadv_orig_node_vlan_free_ref(vlan);
}
print_list:
head = &tt_global_entry->orig_list;
hlist_for_each_entry_rcu(orig_entry, head, list) {
if (best_entry == orig_entry)
continue;
vlan = batadv_orig_node_vlan_get(orig_entry->orig_node,
tt_common_entry->vid);
if (!vlan) {
seq_printf(seq,
" + Cannot retrieve VLAN %d for originator %pM\n",
BATADV_PRINT_VID(tt_common_entry->vid),
orig_entry->orig_node->orig);
continue;
}
last_ttvn = atomic_read(&orig_entry->orig_node->last_ttvn);
seq_printf(seq,
" %c %pM %4d (%3u) via %pM (%3u) (%#.8x) [%c%c%c]\n",
'+', tt_global_entry->common.addr,
BATADV_PRINT_VID(tt_global_entry->common.vid),
orig_entry->ttvn, orig_entry->orig_node->orig,
last_ttvn, vlan->tt.crc,
(flags & BATADV_TT_CLIENT_ROAM ? 'R' : '.'),
(flags & BATADV_TT_CLIENT_WIFI ? 'W' : '.'),
(flags & BATADV_TT_CLIENT_TEMP ? 'T' : '.'));
batadv_orig_node_vlan_free_ref(vlan);
}
}
int batadv_tt_global_seq_print_text(struct seq_file *seq, void *offset)
{
struct net_device *net_dev = (struct net_device *)seq->private;
struct batadv_priv *bat_priv = netdev_priv(net_dev);
struct batadv_hashtable *hash = bat_priv->tt.global_hash;
struct batadv_tt_common_entry *tt_common_entry;
struct batadv_tt_global_entry *tt_global;
struct batadv_hard_iface *primary_if;
struct hlist_head *head;
uint32_t i;
primary_if = batadv_seq_print_text_primary_if_get(seq);
if (!primary_if)
goto out;
seq_printf(seq,
"Globally announced TT entries received via the mesh %s\n",
net_dev->name);
seq_printf(seq, " %-13s %s %s %-15s %s (%-10s) %s\n",
"Client", "VID", "(TTVN)", "Originator", "(Curr TTVN)",
"CRC", "Flags");
for (i = 0; i < hash->size; i++) {
head = &hash->table[i];
rcu_read_lock();
hlist_for_each_entry_rcu(tt_common_entry,
head, hash_entry) {
tt_global = container_of(tt_common_entry,
struct batadv_tt_global_entry,
common);
batadv_tt_global_print_entry(tt_global, seq);
}
rcu_read_unlock();
}
out:
if (primary_if)
batadv_hardif_free_ref(primary_if);
return 0;
}
/* 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) {
hlist_del_rcu(&orig_entry->list);
batadv_tt_global_size_dec(orig_entry->orig_node,
tt_global_entry->common.vid);
batadv_tt_orig_list_entry_free_ref(orig_entry);
}
spin_unlock_bh(&tt_global_entry->list_lock);
}
static void
batadv_tt_global_del_orig_entry(struct batadv_priv *bat_priv,
struct batadv_tt_global_entry *tt_global_entry,
struct batadv_orig_node *orig_node,
const char *message)
{
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);
hlist_del_rcu(&orig_entry->list);
batadv_tt_global_size_dec(orig_node,
tt_global_entry->common.vid);
batadv_tt_orig_list_entry_free_ref(orig_entry);
}
}
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_entry(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 soft 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_entry(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:
if (tt_global_entry)
batadv_tt_global_entry_free_ref(tt_global_entry);
if (local_entry)
batadv_tt_local_entry_free_ref(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 soft 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,
int32_t match_vid,
const char *message)
{
struct batadv_tt_global_entry *tt_global;
struct batadv_tt_common_entry *tt_common_entry;
uint32_t 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_entry(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,
"Deleting global tt entry %pM (vid: %d): %s\n",
tt_global->common.addr,
BATADV_PRINT_VID(vid), message);
hlist_del_rcu(&tt_common_entry->hash_entry);
batadv_tt_global_entry_free_ref(tt_global);
}
}
spin_unlock_bh(list_lock);
}
orig_node->tt_initialised = false;
}
static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global,
char **msg)
{
bool purge = false;
unsigned long roam_timeout = BATADV_TT_CLIENT_ROAM_TIMEOUT;
unsigned long temp_timeout = BATADV_TT_CLIENT_TEMP_TIMEOUT;
if ((tt_global->common.flags & BATADV_TT_CLIENT_ROAM) &&
batadv_has_timed_out(tt_global->roam_at, roam_timeout)) {
purge = true;
*msg = "Roaming timeout\n";
}
if ((tt_global->common.flags & BATADV_TT_CLIENT_TEMP) &&
batadv_has_timed_out(tt_global->common.added_at, temp_timeout)) {
purge = true;
*msg = "Temporary client timeout\n";
}
return purge;
}
static void batadv_tt_global_purge(struct batadv_priv *bat_priv)
{
struct batadv_hashtable *hash = bat_priv->tt.global_hash;
struct hlist_head *head;
struct hlist_node *node_tmp;
spinlock_t *list_lock; /* protects write access to the hash lists */
uint32_t i;
char *msg = NULL;
struct batadv_tt_common_entry *tt_common;
struct batadv_tt_global_entry *tt_global;
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, node_tmp, head,
hash_entry) {
tt_global = container_of(tt_common,
struct batadv_tt_global_entry,
common);
if (!batadv_tt_global_to_purge(tt_global, &msg))
continue;
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),
msg);
hlist_del_rcu(&tt_common->hash_entry);
batadv_tt_global_entry_free_ref(tt_global);
}
spin_unlock_bh(list_lock);
}
}
static void batadv_tt_global_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_global_entry *tt_global;
struct hlist_node *node_tmp;
struct hlist_head *head;
uint32_t i;
if (!bat_priv->tt.global_hash)
return;
hash = bat_priv->tt.global_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_global = container_of(tt_common_entry,
struct batadv_tt_global_entry,
common);
batadv_tt_global_entry_free_ref(tt_global);
}
spin_unlock_bh(list_lock);
}
batadv_hash_destroy(hash);
bat_priv->tt.global_hash = NULL;
}
static bool
_batadv_is_ap_isolated(struct batadv_tt_local_entry *tt_local_entry,
struct batadv_tt_global_entry *tt_global_entry)
{
bool ret = false;
if (tt_local_entry->common.flags & BATADV_TT_CLIENT_WIFI &&
tt_global_entry->common.flags & BATADV_TT_CLIENT_WIFI)
ret = true;
return ret;
}
/**
* batadv_transtable_search - get the mesh destination for a given client
* @bat_priv: the bat priv with all the soft interface information
* @src: mac address of the source client
* @addr: mac address of the destination client
* @vid: VLAN identifier
*
* Returns a pointer to the originator that was selected as destination in the
* mesh for contacting the client 'addr', NULL otherwise.
* In case of multiple originators serving the same client, the function returns
* the best one (best in terms of metric towards the destination node).
*
* If the two clients are AP isolated the function returns NULL.
*/
struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv,
const uint8_t *src,
const uint8_t *addr,
unsigned short vid)
{
struct batadv_tt_local_entry *tt_local_entry = NULL;
struct batadv_tt_global_entry *tt_global_entry = NULL;
struct batadv_orig_node *orig_node = NULL;
struct batadv_tt_orig_list_entry *best_entry;
bool ap_isolation_enabled = false;
struct batadv_softif_vlan *vlan;
/* if the AP isolation is requested on a VLAN, then check for its
* setting in the proper VLAN private data structure
*/
vlan = batadv_softif_vlan_get(bat_priv, vid);
if (vlan) {
ap_isolation_enabled = atomic_read(&vlan->ap_isolation);
batadv_softif_vlan_free_ref(vlan);
}
if (src && ap_isolation_enabled) {
tt_local_entry = batadv_tt_local_hash_find(bat_priv, src, vid);
if (!tt_local_entry ||
(tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING))
goto out;
}
tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid);
if (!tt_global_entry)
goto out;
/* check whether the clients should not communicate due to AP
* isolation
*/
if (tt_local_entry &&
_batadv_is_ap_isolated(tt_local_entry, tt_global_entry))
goto out;
rcu_read_lock();
best_entry = batadv_transtable_best_orig(tt_global_entry);
/* found anything? */
if (best_entry)
orig_node = best_entry->orig_node;
if (orig_node && !atomic_inc_not_zero(&orig_node->refcount))
orig_node = NULL;
rcu_read_unlock();
out:
if (tt_global_entry)
batadv_tt_global_entry_free_ref(tt_global_entry);
if (tt_local_entry)
batadv_tt_local_entry_free_ref(tt_local_entry);
return orig_node;
}
/**
* batadv_tt_global_crc - calculates the checksum of the local table belonging
* to the given orig_node
* @bat_priv: the bat priv with all the soft interface information
* @orig_node: originator for which the CRC should be computed
* @vid: VLAN identifier for which the CRC32 has to be computed
*
* This function computes the checksum for the global table corresponding to a
* specific originator. In particular, the checksum is computed as follows: For
* each client connected to the originator the CRC32C of the MAC address and the
* VID is computed and then all the CRC32Cs of the various clients are xor'ed
* together.
*
* The idea behind is that CRC32C should be used as much as possible in order to
* produce a unique hash of the table, but since the order which is used to feed
* the CRC32C function affects the result and since every node in the network
* probably sorts the clients differently, the hash function cannot be directly
* computed over the entire table. Hence the CRC32C is used only on
* the single client entry, while all the results are then xor'ed together
* because the XOR operation can combine them all while trying to reduce the
* noise as much as possible.
*
* Returns the checksum of the global table of a given originator.
*/
static uint32_t batadv_tt_global_crc(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node,
unsigned short vid)
{
struct batadv_hashtable *hash = bat_priv->tt.global_hash;
struct batadv_tt_common_entry *tt_common;
struct batadv_tt_global_entry *tt_global;
struct hlist_head *head;
uint32_t i, crc_tmp, crc = 0;
for (i = 0; i < hash->size; i++) {
head = &hash->table[i];
rcu_read_lock();
hlist_for_each_entry_rcu(tt_common, head, hash_entry) {
tt_global = container_of(tt_common,
struct batadv_tt_global_entry,
common);
/* compute the CRC only for entries belonging to the
* VLAN identified by the vid passed as parameter
*/
if (tt_common->vid != vid)
continue;
/* Roaming clients are in the global table for
* consistency only. They don't have to be
* taken into account while computing the
* global crc
*/
if (tt_common->flags & BATADV_TT_CLIENT_ROAM)
continue;
/* Temporary clients have not been announced yet, so
* they have to be skipped while computing the global
* crc
*/
if (tt_common->flags & BATADV_TT_CLIENT_TEMP)
continue;
/* find out if this global entry is announced by this
* originator
*/
if (!batadv_tt_global_entry_has_orig(tt_global,
orig_node))
continue;
crc_tmp = crc32c(0, &tt_common->vid,
sizeof(tt_common->vid));
crc ^= crc32c(crc_tmp, tt_common->addr, ETH_ALEN);
}
rcu_read_unlock();
}
return crc;
}
/**
* batadv_tt_local_crc - calculates the checksum of the local table
* @bat_priv: the bat priv with all the soft interface information
* @vid: VLAN identifier for which the CRC32 has to be computed
*
* For details about the computation, please refer to the documentation for
* batadv_tt_global_crc().
*
* Returns the checksum of the local table
*/
static uint32_t batadv_tt_local_crc(struct batadv_priv *bat_priv,
unsigned short vid)
{
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
struct batadv_tt_common_entry *tt_common;
struct hlist_head *head;
uint32_t i, crc_tmp, crc = 0;
for (i = 0; i < hash->size; i++) {
head = &hash->table[i];
rcu_read_lock();
hlist_for_each_entry_rcu(tt_common, head, hash_entry) {
/* compute the CRC only for entries belonging to the
* VLAN identified by vid
*/
if (tt_common->vid != vid)
continue;
/* not yet committed clients have not to be taken into
* account while computing the CRC
*/
if (tt_common->flags & BATADV_TT_CLIENT_NEW)
continue;
crc_tmp = crc32c(0, &tt_common->vid,
sizeof(tt_common->vid));
crc ^= crc32c(crc_tmp, tt_common->addr, ETH_ALEN);
}
rcu_read_unlock();
}
return crc;
}
static void batadv_tt_req_list_free(struct batadv_priv *bat_priv)
{
struct batadv_tt_req_node *node, *safe;
spin_lock_bh(&bat_priv->tt.req_list_lock);
list_for_each_entry_safe(node, safe, &bat_priv->tt.req_list, list) {
list_del(&node->list);
kfree(node);
}
spin_unlock_bh(&bat_priv->tt.req_list_lock);
}
static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node,
const void *tt_buff,
uint16_t tt_buff_len)
{
/* Replace the old buffer only if I received something in the
* last OGM (the OGM could carry no changes)
*/
spin_lock_bh(&orig_node->tt_buff_lock);
if (tt_buff_len > 0) {
kfree(orig_node->tt_buff);
orig_node->tt_buff_len = 0;
orig_node->tt_buff = kmalloc(tt_buff_len, GFP_ATOMIC);
if (orig_node->tt_buff) {
memcpy(orig_node->tt_buff, tt_buff, tt_buff_len);
orig_node->tt_buff_len = tt_buff_len;
}
}
spin_unlock_bh(&orig_node->tt_buff_lock);
}
static void batadv_tt_req_purge(struct batadv_priv *bat_priv)
{
struct batadv_tt_req_node *node, *safe;
spin_lock_bh(&bat_priv->tt.req_list_lock);
list_for_each_entry_safe(node, safe, &bat_priv->tt.req_list, list) {
if (batadv_has_timed_out(node->issued_at,
BATADV_TT_REQUEST_TIMEOUT)) {
list_del(&node->list);
kfree(node);
}
}
spin_unlock_bh(&bat_priv->tt.req_list_lock);
}
/* returns the pointer to the new tt_req_node struct if no request
* has already been issued for this orig_node, NULL otherwise
*/
static struct batadv_tt_req_node *
batadv_new_tt_req_node(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node)
{
struct batadv_tt_req_node *tt_req_node_tmp, *tt_req_node = NULL;
spin_lock_bh(&bat_priv->tt.req_list_lock);
list_for_each_entry(tt_req_node_tmp, &bat_priv->tt.req_list, list) {
if (batadv_compare_eth(tt_req_node_tmp, orig_node) &&
!batadv_has_timed_out(tt_req_node_tmp->issued_at,
BATADV_TT_REQUEST_TIMEOUT))
goto unlock;
}
tt_req_node = kmalloc(sizeof(*tt_req_node), GFP_ATOMIC);
if (!tt_req_node)
goto unlock;
memcpy(tt_req_node->addr, orig_node->orig, ETH_ALEN);
tt_req_node->issued_at = jiffies;
list_add(&tt_req_node->list, &bat_priv->tt.req_list);
unlock:
spin_unlock_bh(&bat_priv->tt.req_list_lock);
return tt_req_node;
}
/**
* batadv_tt_local_valid - verify that given tt entry is a valid one
* @entry_ptr: to be checked local tt entry
* @data_ptr: not used but definition required to satisfy the callback prototype
*
* Returns 1 if the entry is a valid, 0 otherwise.
*/
static int batadv_tt_local_valid(const void *entry_ptr, const void *data_ptr)
{
const struct batadv_tt_common_entry *tt_common_entry = entry_ptr;
if (tt_common_entry->flags & BATADV_TT_CLIENT_NEW)
return 0;
return 1;
}
static int batadv_tt_global_valid(const void *entry_ptr,
const void *data_ptr)
{
const struct batadv_tt_common_entry *tt_common_entry = entry_ptr;
const struct batadv_tt_global_entry *tt_global_entry;
const struct batadv_orig_node *orig_node = data_ptr;
if (tt_common_entry->flags & BATADV_TT_CLIENT_ROAM ||
tt_common_entry->flags & BATADV_TT_CLIENT_TEMP)
return 0;
tt_global_entry = container_of(tt_common_entry,
struct batadv_tt_global_entry,
common);
return batadv_tt_global_entry_has_orig(tt_global_entry, orig_node);
}
/**
* batadv_tt_tvlv_generate - fill the tvlv buff with the tt entries from the
* specified tt hash
* @bat_priv: the bat priv with all the soft interface information
* @hash: hash table containing the tt entries
* @tt_len: expected tvlv tt data buffer length in number of bytes
* @tvlv_buff: pointer to the buffer to fill with the TT data
* @valid_cb: function to filter tt change entries
* @cb_data: data passed to the filter function as argument
*/
static void batadv_tt_tvlv_generate(struct batadv_priv *bat_priv,
struct batadv_hashtable *hash,
void *tvlv_buff, uint16_t tt_len,
int (*valid_cb)(const void *, const void *),
void *cb_data)
{
struct batadv_tt_common_entry *tt_common_entry;
struct batadv_tvlv_tt_change *tt_change;
struct hlist_head *head;
uint16_t tt_tot, tt_num_entries = 0;
uint32_t i;
tt_tot = batadv_tt_entries(tt_len);
tt_change = (struct batadv_tvlv_tt_change *)tvlv_buff;
rcu_read_lock();
for (i = 0; i < hash->size; i++) {
head = &hash->table[i];
hlist_for_each_entry_rcu(tt_common_entry,
head, hash_entry) {
if (tt_tot == tt_num_entries)
break;
if ((valid_cb) && (!valid_cb(tt_common_entry, cb_data)))
continue;
memcpy(tt_change->addr, tt_common_entry->addr,
ETH_ALEN);
tt_change->flags = tt_common_entry->flags;
tt_change->vid = htons(tt_common_entry->vid);
tt_change->reserved = 0;
tt_num_entries++;
tt_change++;
}
}
rcu_read_unlock();
}
/**
* batadv_tt_global_check_crc - check if all the CRCs are correct
* @orig_node: originator for which the CRCs have to be checked
* @tt_vlan: pointer to the first tvlv VLAN entry
* @num_vlan: number of tvlv VLAN entries
* @create: if true, create VLAN objects if not found
*
* Return true if all the received CRCs match the locally stored ones, false
* otherwise
*/
static bool batadv_tt_global_check_crc(struct batadv_orig_node *orig_node,
struct batadv_tvlv_tt_vlan_data *tt_vlan,
uint16_t num_vlan)
{
struct batadv_tvlv_tt_vlan_data *tt_vlan_tmp;
struct batadv_orig_node_vlan *vlan;
int i;
/* check if each received CRC matches the locally stored one */
for (i = 0; i < num_vlan; i++) {
tt_vlan_tmp = tt_vlan + i;
/* if orig_node is a backbone node for this VLAN, don't check
* the CRC as we ignore all the global entries over it
*/
if (batadv_bla_is_backbone_gw_orig(orig_node->bat_priv,
orig_node->orig,
ntohs(tt_vlan_tmp->vid)))
continue;
vlan = batadv_orig_node_vlan_get(orig_node,
ntohs(tt_vlan_tmp->vid));
if (!vlan)
return false;
if (vlan->tt.crc != ntohl(tt_vlan_tmp->crc))
return false;
}
return true;
}
/**
* batadv_tt_local_update_crc - update all the local CRCs
* @bat_priv: the bat priv with all the soft interface information
*/
static void batadv_tt_local_update_crc(struct batadv_priv *bat_priv)
{
struct batadv_softif_vlan *vlan;
/* recompute the global CRC for each VLAN */
rcu_read_lock();
hlist_for_each_entry_rcu(vlan, &bat_priv->softif_vlan_list, list) {
vlan->tt.crc = batadv_tt_local_crc(bat_priv, vlan->vid);
}
rcu_read_unlock();
}
/**
* batadv_tt_global_update_crc - update all the global CRCs for this orig_node
* @bat_priv: the bat priv with all the soft interface information
* @orig_node: the orig_node for which the CRCs have to be updated
*/
static void batadv_tt_global_update_crc(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node)
{
struct batadv_orig_node_vlan *vlan;
uint32_t crc;
/* recompute the global CRC for each VLAN */
rcu_read_lock();
list_for_each_entry_rcu(vlan, &orig_node->vlan_list, list) {
/* if orig_node is a backbone node for this VLAN, don't compute
* the CRC as we ignore all the global entries over it
*/
if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig,
vlan->vid))
continue;
crc = batadv_tt_global_crc(bat_priv, orig_node, vlan->vid);
vlan->tt.crc = crc;
}
rcu_read_unlock();
}
/**
* batadv_send_tt_request - send a TT Request message to a given node
* @bat_priv: the bat priv with all the soft interface information
* @dst_orig_node: the destination of the message
* @ttvn: the version number that the source of the message is looking for
* @tt_vlan: pointer to the first tvlv VLAN object to request
* @num_vlan: number of tvlv VLAN entries
* @full_table: ask for the entire translation table if true, while only for the
* last TT diff otherwise
*/
static int batadv_send_tt_request(struct batadv_priv *bat_priv,
struct batadv_orig_node *dst_orig_node,
uint8_t ttvn,
struct batadv_tvlv_tt_vlan_data *tt_vlan,
uint16_t num_vlan, bool full_table)
{
struct batadv_tvlv_tt_data *tvlv_tt_data = NULL;
struct batadv_tt_req_node *tt_req_node = NULL;
struct batadv_tvlv_tt_vlan_data *tt_vlan_req;
struct batadv_hard_iface *primary_if;
bool ret = false;
int i, size;
primary_if = batadv_primary_if_get_selected(bat_priv);
if (!primary_if)
goto out;
/* The new tt_req will be issued only if I'm not waiting for a
* reply from the same orig_node yet
*/
tt_req_node = batadv_new_tt_req_node(bat_priv, dst_orig_node);
if (!tt_req_node)
goto out;
size = sizeof(*tvlv_tt_data) + sizeof(*tt_vlan_req) * num_vlan;
tvlv_tt_data = kzalloc(size, GFP_ATOMIC);
if (!tvlv_tt_data)
goto out;
tvlv_tt_data->flags = BATADV_TT_REQUEST;
tvlv_tt_data->ttvn = ttvn;
tvlv_tt_data->num_vlan = htons(num_vlan);
/* send all the CRCs within the request. This is needed by intermediate
* nodes to ensure they have the correct table before replying
*/
tt_vlan_req = (struct batadv_tvlv_tt_vlan_data *)(tvlv_tt_data + 1);
for (i = 0; i < num_vlan; i++) {
tt_vlan_req->vid = tt_vlan->vid;
tt_vlan_req->crc = tt_vlan->crc;
tt_vlan_req++;
tt_vlan++;
}
if (full_table)
tvlv_tt_data->flags |= BATADV_TT_FULL_TABLE;
batadv_dbg(BATADV_DBG_TT, bat_priv, "Sending TT_REQUEST to %pM [%c]\n",
dst_orig_node->orig, full_table ? 'F' : '.');
batadv_inc_counter(bat_priv, BATADV_CNT_TT_REQUEST_TX);
batadv_tvlv_unicast_send(bat_priv, primary_if->net_dev->dev_addr,
dst_orig_node->orig, BATADV_TVLV_TT, 1,
tvlv_tt_data, size);
ret = true;
out:
if (primary_if)
batadv_hardif_free_ref(primary_if);
if (ret && tt_req_node) {
spin_lock_bh(&bat_priv->tt.req_list_lock);
list_del(&tt_req_node->list);
spin_unlock_bh(&bat_priv->tt.req_list_lock);
kfree(tt_req_node);
}
kfree(tvlv_tt_data);
return ret;
}
/**
* batadv_send_other_tt_response - send reply to tt request concerning another
* node's translation table
* @bat_priv: the bat priv with all the soft interface information
* @tt_data: tt data containing the tt request information
* @req_src: mac address of tt request sender
* @req_dst: mac address of tt request recipient
*
* Returns true if tt request reply was sent, false otherwise.
*/
static bool batadv_send_other_tt_response(struct batadv_priv *bat_priv,
struct batadv_tvlv_tt_data *tt_data,
uint8_t *req_src, uint8_t *req_dst)
{
struct batadv_orig_node *req_dst_orig_node;
struct batadv_orig_node *res_dst_orig_node = NULL;
struct batadv_tvlv_tt_change *tt_change;
struct batadv_tvlv_tt_data *tvlv_tt_data = NULL;
struct batadv_tvlv_tt_vlan_data *tt_vlan;
bool ret = false, full_table;
uint8_t orig_ttvn, req_ttvn;
uint16_t tvlv_len;
int32_t tt_len;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Received TT_REQUEST from %pM for ttvn: %u (%pM) [%c]\n",
req_src, tt_data->ttvn, req_dst,
(tt_data->flags & BATADV_TT_FULL_TABLE ? 'F' : '.'));
/* Let's get the orig node of the REAL destination */
req_dst_orig_node = batadv_orig_hash_find(bat_priv, req_dst);
if (!req_dst_orig_node)
goto out;
res_dst_orig_node = batadv_orig_hash_find(bat_priv, req_src);
if (!res_dst_orig_node)
goto out;
orig_ttvn = (uint8_t)atomic_read(&req_dst_orig_node->last_ttvn);
req_ttvn = tt_data->ttvn;
tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(tt_data + 1);
/* this node doesn't have the requested data */
if (orig_ttvn != req_ttvn ||
!batadv_tt_global_check_crc(req_dst_orig_node, tt_vlan,
ntohs(tt_data->num_vlan)))
goto out;
/* If the full table has been explicitly requested */
if (tt_data->flags & BATADV_TT_FULL_TABLE ||
!req_dst_orig_node->tt_buff)
full_table = true;
else
full_table = false;
/* TT fragmentation hasn't been implemented yet, so send as many
* TT entries fit a single packet as possible only
*/
if (!full_table) {
spin_lock_bh(&req_dst_orig_node->tt_buff_lock);
tt_len = req_dst_orig_node->tt_buff_len;
tvlv_len = batadv_tt_prepare_tvlv_global_data(req_dst_orig_node,
&tvlv_tt_data,
&tt_change,
&tt_len);
if (!tt_len)
goto unlock;
/* Copy the last orig_node's OGM buffer */
memcpy(tt_change, req_dst_orig_node->tt_buff,
req_dst_orig_node->tt_buff_len);
spin_unlock_bh(&req_dst_orig_node->tt_buff_lock);
} else {
/* allocate the tvlv, put the tt_data and all the tt_vlan_data
* in the initial part
*/
tt_len = -1;
tvlv_len = batadv_tt_prepare_tvlv_global_data(req_dst_orig_node,
&tvlv_tt_data,
&tt_change,
&tt_len);
if (!tt_len)
goto out;
/* fill the rest of the tvlv with the real TT entries */
batadv_tt_tvlv_generate(bat_priv, bat_priv->tt.global_hash,
tt_change, tt_len,
batadv_tt_global_valid,
req_dst_orig_node);
}
tvlv_tt_data->flags = BATADV_TT_RESPONSE;
tvlv_tt_data->ttvn = req_ttvn;
if (full_table)
tvlv_tt_data->flags |= BATADV_TT_FULL_TABLE;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Sending TT_RESPONSE %pM for %pM [%c] (ttvn: %u)\n",
res_dst_orig_node->orig, req_dst_orig_node->orig,
full_table ? 'F' : '.', req_ttvn);
batadv_inc_counter(bat_priv, BATADV_CNT_TT_RESPONSE_TX);
batadv_tvlv_unicast_send(bat_priv, req_dst_orig_node->orig,
req_src, BATADV_TVLV_TT, 1, tvlv_tt_data,
tvlv_len);
ret = true;
goto out;
unlock:
spin_unlock_bh(&req_dst_orig_node->tt_buff_lock);
out:
if (res_dst_orig_node)
batadv_orig_node_free_ref(res_dst_orig_node);
if (req_dst_orig_node)
batadv_orig_node_free_ref(req_dst_orig_node);
kfree(tvlv_tt_data);
return ret;
}
/**
* batadv_send_my_tt_response - send reply to tt request concerning this node's
* translation table
* @bat_priv: the bat priv with all the soft interface information
* @tt_data: tt data containing the tt request information
* @req_src: mac address of tt request sender
*
* Returns true if tt request reply was sent, false otherwise.
*/
static bool batadv_send_my_tt_response(struct batadv_priv *bat_priv,
struct batadv_tvlv_tt_data *tt_data,
uint8_t *req_src)
{
struct batadv_tvlv_tt_data *tvlv_tt_data = NULL;
struct batadv_hard_iface *primary_if = NULL;
struct batadv_tvlv_tt_change *tt_change;
struct batadv_orig_node *orig_node;
uint8_t my_ttvn, req_ttvn;
uint16_t tvlv_len;
bool full_table;
int32_t tt_len;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Received TT_REQUEST from %pM for ttvn: %u (me) [%c]\n",
req_src, tt_data->ttvn,
(tt_data->flags & BATADV_TT_FULL_TABLE ? 'F' : '.'));
spin_lock_bh(&bat_priv->tt.commit_lock);
my_ttvn = (uint8_t)atomic_read(&bat_priv->tt.vn);
req_ttvn = tt_data->ttvn;
orig_node = batadv_orig_hash_find(bat_priv, req_src);
if (!orig_node)
goto out;
primary_if = batadv_primary_if_get_selected(bat_priv);
if (!primary_if)
goto out;
/* If the full table has been explicitly requested or the gap
* is too big send the whole local translation table
*/
if (tt_data->flags & BATADV_TT_FULL_TABLE || my_ttvn != req_ttvn ||
!bat_priv->tt.last_changeset)
full_table = true;
else
full_table = false;
/* TT fragmentation hasn't been implemented yet, so send as many
* TT entries fit a single packet as possible only
*/
if (!full_table) {
spin_lock_bh(&bat_priv->tt.last_changeset_lock);
tt_len = bat_priv->tt.last_changeset_len;
tvlv_len = batadv_tt_prepare_tvlv_local_data(bat_priv,
&tvlv_tt_data,
&tt_change,
&tt_len);
if (!tt_len)
goto unlock;
/* Copy the last orig_node's OGM buffer */
memcpy(tt_change, bat_priv->tt.last_changeset,
bat_priv->tt.last_changeset_len);
spin_unlock_bh(&bat_priv->tt.last_changeset_lock);
} else {
req_ttvn = (uint8_t)atomic_read(&bat_priv->tt.vn);
/* allocate the tvlv, put the tt_data and all the tt_vlan_data
* in the initial part
*/
tt_len = -1;
tvlv_len = batadv_tt_prepare_tvlv_local_data(bat_priv,
&tvlv_tt_data,
&tt_change,
&tt_len);
if (!tt_len)
goto out;
/* fill the rest of the tvlv with the real TT entries */
batadv_tt_tvlv_generate(bat_priv, bat_priv->tt.local_hash,
tt_change, tt_len,
batadv_tt_local_valid, NULL);
}
tvlv_tt_data->flags = BATADV_TT_RESPONSE;
tvlv_tt_data->ttvn = req_ttvn;
if (full_table)
tvlv_tt_data->flags |= BATADV_TT_FULL_TABLE;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Sending TT_RESPONSE to %pM [%c] (ttvn: %u)\n",
orig_node->orig, full_table ? 'F' : '.', req_ttvn);
batadv_inc_counter(bat_priv, BATADV_CNT_TT_RESPONSE_TX);
batadv_tvlv_unicast_send(bat_priv, primary_if->net_dev->dev_addr,
req_src, BATADV_TVLV_TT, 1, tvlv_tt_data,
tvlv_len);
goto out;
unlock:
spin_unlock_bh(&bat_priv->tt.last_changeset_lock);
out:
spin_unlock_bh(&bat_priv->tt.commit_lock);
if (orig_node)
batadv_orig_node_free_ref(orig_node);
if (primary_if)
batadv_hardif_free_ref(primary_if);
kfree(tvlv_tt_data);
/* The packet was for this host, so it doesn't need to be re-routed */
return true;
}
/**
* batadv_send_tt_response - send reply to tt request
* @bat_priv: the bat priv with all the soft interface information
* @tt_data: tt data containing the tt request information
* @req_src: mac address of tt request sender
* @req_dst: mac address of tt request recipient
*
* Returns true if tt request reply was sent, false otherwise.
*/
static bool batadv_send_tt_response(struct batadv_priv *bat_priv,
struct batadv_tvlv_tt_data *tt_data,
uint8_t *req_src, uint8_t *req_dst)
{
if (batadv_is_my_mac(bat_priv, req_dst))
return batadv_send_my_tt_response(bat_priv, tt_data, req_src);
else
return batadv_send_other_tt_response(bat_priv, tt_data,
req_src, req_dst);
}
static void _batadv_tt_update_changes(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node,
struct batadv_tvlv_tt_change *tt_change,
uint16_t tt_num_changes, uint8_t ttvn)
{
int i;
int roams;
for (i = 0; i < tt_num_changes; i++) {
if ((tt_change + i)->flags & BATADV_TT_CLIENT_DEL) {
roams = (tt_change + i)->flags & BATADV_TT_CLIENT_ROAM;
batadv_tt_global_del(bat_priv, orig_node,
(tt_change + i)->addr,
ntohs((tt_change + i)->vid),
"tt removed by changes",
roams);
} else {
if (!batadv_tt_global_add(bat_priv, orig_node,
(tt_change + i)->addr,
ntohs((tt_change + i)->vid),
(tt_change + i)->flags, ttvn))
/* In case of problem while storing a
* global_entry, we stop the updating
* procedure without committing the
* ttvn change. This will avoid to send
* corrupted data on tt_request
*/
return;
}
}
orig_node->tt_initialised = true;
}
static void batadv_tt_fill_gtable(struct batadv_priv *bat_priv,
struct batadv_tvlv_tt_change *tt_change,
uint8_t ttvn, uint8_t *resp_src,
uint16_t num_entries)
{
struct batadv_orig_node *orig_node;
orig_node = batadv_orig_hash_find(bat_priv, resp_src);
if (!orig_node)
goto out;
/* Purge the old table first.. */
batadv_tt_global_del_orig(bat_priv, orig_node, -1,
"Received full table");
_batadv_tt_update_changes(bat_priv, orig_node, tt_change, num_entries,
ttvn);
spin_lock_bh(&orig_node->tt_buff_lock);
kfree(orig_node->tt_buff);
orig_node->tt_buff_len = 0;
orig_node->tt_buff = NULL;
spin_unlock_bh(&orig_node->tt_buff_lock);
atomic_set(&orig_node->last_ttvn, ttvn);
out:
if (orig_node)
batadv_orig_node_free_ref(orig_node);
}
static void batadv_tt_update_changes(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node,
uint16_t tt_num_changes, uint8_t ttvn,
struct batadv_tvlv_tt_change *tt_change)
{
_batadv_tt_update_changes(bat_priv, orig_node, tt_change,
tt_num_changes, ttvn);
batadv_tt_save_orig_buffer(bat_priv, orig_node, tt_change,
batadv_tt_len(tt_num_changes));
atomic_set(&orig_node->last_ttvn, ttvn);
}
/**
* batadv_is_my_client - check if a client is served by the local node
* @bat_priv: the bat priv with all the soft interface information
* @addr: the mac adress of the client to check
* @vid: VLAN identifier
*
* Returns true if the client is served by this node, false otherwise.
*/
bool batadv_is_my_client(struct batadv_priv *bat_priv, const uint8_t *addr,
unsigned short vid)
{
struct batadv_tt_local_entry *tt_local_entry;
bool ret = false;
tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid);
if (!tt_local_entry)
goto out;
/* Check if the client has been logically deleted (but is kept for
* consistency purpose)
*/
if ((tt_local_entry->common.flags & BATADV_TT_CLIENT_PENDING) ||
(tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM))
goto out;
ret = true;
out:
if (tt_local_entry)
batadv_tt_local_entry_free_ref(tt_local_entry);
return ret;
}
/**
* batadv_handle_tt_response - process incoming tt reply
* @bat_priv: the bat priv with all the soft interface information
* @tt_data: tt data containing the tt request information
* @resp_src: mac address of tt reply sender
* @num_entries: number of tt change entries appended to the tt data
*/
static void batadv_handle_tt_response(struct batadv_priv *bat_priv,
struct batadv_tvlv_tt_data *tt_data,
uint8_t *resp_src, uint16_t num_entries)
{
struct batadv_tt_req_node *node, *safe;
struct batadv_orig_node *orig_node = NULL;
struct batadv_tvlv_tt_change *tt_change;
uint8_t *tvlv_ptr = (uint8_t *)tt_data;
uint16_t change_offset;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Received TT_RESPONSE from %pM for ttvn %d t_size: %d [%c]\n",
resp_src, tt_data->ttvn, num_entries,
(tt_data->flags & BATADV_TT_FULL_TABLE ? 'F' : '.'));
orig_node = batadv_orig_hash_find(bat_priv, resp_src);
if (!orig_node)
goto out;
spin_lock_bh(&orig_node->tt_lock);
change_offset = sizeof(struct batadv_tvlv_tt_vlan_data);
change_offset *= ntohs(tt_data->num_vlan);
change_offset += sizeof(*tt_data);
tvlv_ptr += change_offset;
tt_change = (struct batadv_tvlv_tt_change *)tvlv_ptr;
if (tt_data->flags & BATADV_TT_FULL_TABLE) {
batadv_tt_fill_gtable(bat_priv, tt_change, tt_data->ttvn,
resp_src, num_entries);
} else {
batadv_tt_update_changes(bat_priv, orig_node, num_entries,
tt_data->ttvn, tt_change);
}
/* Recalculate the CRC for this orig_node and store it */
batadv_tt_global_update_crc(bat_priv, orig_node);
spin_unlock_bh(&orig_node->tt_lock);
/* Delete the tt_req_node from pending tt_requests list */
spin_lock_bh(&bat_priv->tt.req_list_lock);
list_for_each_entry_safe(node, safe, &bat_priv->tt.req_list, list) {
if (!batadv_compare_eth(node->addr, resp_src))
continue;
list_del(&node->list);
kfree(node);
}
spin_unlock_bh(&bat_priv->tt.req_list_lock);
out:
if (orig_node)
batadv_orig_node_free_ref(orig_node);
}
static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv)
{
struct batadv_tt_roam_node *node, *safe;
spin_lock_bh(&bat_priv->tt.roam_list_lock);
list_for_each_entry_safe(node, safe, &bat_priv->tt.roam_list, list) {
list_del(&node->list);
kfree(node);
}
spin_unlock_bh(&bat_priv->tt.roam_list_lock);
}
static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
{
struct batadv_tt_roam_node *node, *safe;
spin_lock_bh(&bat_priv->tt.roam_list_lock);
list_for_each_entry_safe(node, safe, &bat_priv->tt.roam_list, list) {
if (!batadv_has_timed_out(node->first_time,
BATADV_ROAMING_MAX_TIME))
continue;
list_del(&node->list);
kfree(node);
}
spin_unlock_bh(&bat_priv->tt.roam_list_lock);
}
/* This function checks whether the client already reached the
* maximum number of possible roaming phases. In this case the ROAMING_ADV
* will not be sent.
*
* returns true if the ROAMING_ADV can be sent, false otherwise
*/
static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv,
uint8_t *client)
{
struct batadv_tt_roam_node *tt_roam_node;
bool ret = false;
spin_lock_bh(&bat_priv->tt.roam_list_lock);
/* The new tt_req will be issued only if I'm not waiting for a
* reply from the same orig_node yet
*/
list_for_each_entry(tt_roam_node, &bat_priv->tt.roam_list, list) {
if (!batadv_compare_eth(tt_roam_node->addr, client))
continue;
if (batadv_has_timed_out(tt_roam_node->first_time,
BATADV_ROAMING_MAX_TIME))
continue;
if (!batadv_atomic_dec_not_zero(&tt_roam_node->counter))
/* Sorry, you roamed too many times! */
goto unlock;
ret = true;
break;
}
if (!ret) {
tt_roam_node = kmalloc(sizeof(*tt_roam_node), GFP_ATOMIC);
if (!tt_roam_node)
goto unlock;
tt_roam_node->first_time = jiffies;
atomic_set(&tt_roam_node->counter,
BATADV_ROAMING_MAX_COUNT - 1);
memcpy(tt_roam_node->addr, client, ETH_ALEN);
list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
ret = true;
}
unlock:
spin_unlock_bh(&bat_priv->tt.roam_list_lock);
return ret;
}
/**
* batadv_send_roam_adv - send a roaming advertisement message
* @bat_priv: the bat priv with all the soft interface information
* @client: mac address of the roaming client
* @vid: VLAN identifier
* @orig_node: message destination
*
* Send a ROAMING_ADV message to the node which was previously serving this
* client. This is done to inform the node that from now on all traffic destined
* for this particular roamed client has to be forwarded to the sender of the
* roaming message.
*/
static void batadv_send_roam_adv(struct batadv_priv *bat_priv, uint8_t *client,
unsigned short vid,
struct batadv_orig_node *orig_node)
{
struct batadv_hard_iface *primary_if;
struct batadv_tvlv_roam_adv tvlv_roam;
primary_if = batadv_primary_if_get_selected(bat_priv);
if (!primary_if)
goto out;
/* before going on we have to check whether the client has
* already roamed to us too many times
*/
if (!batadv_tt_check_roam_count(bat_priv, client))
goto out;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Sending ROAMING_ADV to %pM (client %pM, vid: %d)\n",
orig_node->orig, client, BATADV_PRINT_VID(vid));
batadv_inc_counter(bat_priv, BATADV_CNT_TT_ROAM_ADV_TX);
memcpy(tvlv_roam.client, client, sizeof(tvlv_roam.client));
tvlv_roam.vid = htons(vid);
batadv_tvlv_unicast_send(bat_priv, primary_if->net_dev->dev_addr,
orig_node->orig, BATADV_TVLV_ROAM, 1,
&tvlv_roam, sizeof(tvlv_roam));
out:
if (primary_if)
batadv_hardif_free_ref(primary_if);
}
static void batadv_tt_purge(struct work_struct *work)
{
struct delayed_work *delayed_work;
struct batadv_priv_tt *priv_tt;
struct batadv_priv *bat_priv;
delayed_work = container_of(work, struct delayed_work, work);
priv_tt = container_of(delayed_work, struct batadv_priv_tt, work);
bat_priv = container_of(priv_tt, struct batadv_priv, tt);
batadv_tt_local_purge(bat_priv);
batadv_tt_global_purge(bat_priv);
batadv_tt_req_purge(bat_priv);
batadv_tt_roam_purge(bat_priv);
queue_delayed_work(batadv_event_workqueue, &bat_priv->tt.work,
msecs_to_jiffies(BATADV_TT_WORK_PERIOD));
}
void batadv_tt_free(struct batadv_priv *bat_priv)
{
batadv_tvlv_container_unregister(bat_priv, BATADV_TVLV_TT, 1);
batadv_tvlv_handler_unregister(bat_priv, BATADV_TVLV_TT, 1);
cancel_delayed_work_sync(&bat_priv->tt.work);
batadv_tt_local_table_free(bat_priv);
batadv_tt_global_table_free(bat_priv);
batadv_tt_req_list_free(bat_priv);
batadv_tt_changes_list_free(bat_priv);
batadv_tt_roam_list_free(bat_priv);
kfree(bat_priv->tt.last_changeset);
}
/**
* batadv_tt_local_set_flags - set or unset the specified flags on the local
* table and possibly count them in the TT size
* @bat_priv: the bat priv with all the soft interface information
* @flags: the flag to switch
* @enable: whether to set or unset the flag
* @count: whether to increase the TT size by the number of changed entries
*/
static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv,
uint16_t flags, bool enable, bool count)
{
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
struct batadv_tt_common_entry *tt_common_entry;
uint16_t changed_num = 0;
struct hlist_head *head;
uint32_t i;
if (!hash)
return;
for (i = 0; i < hash->size; i++) {
head = &hash->table[i];
rcu_read_lock();
hlist_for_each_entry_rcu(tt_common_entry,
head, hash_entry) {
if (enable) {
if ((tt_common_entry->flags & flags) == flags)
continue;
tt_common_entry->flags |= flags;
} else {
if (!(tt_common_entry->flags & flags))
continue;
tt_common_entry->flags &= ~flags;
}
changed_num++;
if (!count)
continue;
batadv_tt_local_size_inc(bat_priv,
tt_common_entry->vid);
}
rcu_read_unlock();
}
}
/* Purge out all the tt local entries marked with BATADV_TT_CLIENT_PENDING */
static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv)
{
struct batadv_hashtable *hash = bat_priv->tt.local_hash;
struct batadv_tt_common_entry *tt_common;
struct batadv_tt_local_entry *tt_local;
struct hlist_node *node_tmp;
struct hlist_head *head;
spinlock_t *list_lock; /* protects write access to the hash lists */
uint32_t i;
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, node_tmp, head,
hash_entry) {
if (!(tt_common->flags & BATADV_TT_CLIENT_PENDING))
continue;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Deleting local tt entry (%pM, vid: %d): pending\n",
tt_common->addr,
BATADV_PRINT_VID(tt_common->vid));
batadv_tt_local_size_dec(bat_priv, tt_common->vid);
hlist_del_rcu(&tt_common->hash_entry);
tt_local = container_of(tt_common,
struct batadv_tt_local_entry,
common);
batadv_tt_local_entry_free_ref(tt_local);
}
spin_unlock_bh(list_lock);
}
}
/**
* batadv_tt_local_commit_changes - commit all pending local tt changes which
* have been queued in the time since the last commit
* @bat_priv: the bat priv with all the soft interface information
*/
void batadv_tt_local_commit_changes(struct batadv_priv *bat_priv)
{
spin_lock_bh(&bat_priv->tt.commit_lock);
if (atomic_read(&bat_priv->tt.local_changes) < 1) {
if (!batadv_atomic_dec_not_zero(&bat_priv->tt.ogm_append_cnt))
batadv_tt_tvlv_container_update(bat_priv);
goto out;
}
batadv_tt_local_set_flags(bat_priv, BATADV_TT_CLIENT_NEW, false, true);
batadv_tt_local_purge_pending_clients(bat_priv);
batadv_tt_local_update_crc(bat_priv);
/* Increment the TTVN only once per OGM interval */
atomic_inc(&bat_priv->tt.vn);
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Local changes committed, updating to ttvn %u\n",
(uint8_t)atomic_read(&bat_priv->tt.vn));
/* reset the sending counter */
atomic_set(&bat_priv->tt.ogm_append_cnt, BATADV_TT_OGM_APPEND_MAX);
batadv_tt_tvlv_container_update(bat_priv);
out:
spin_unlock_bh(&bat_priv->tt.commit_lock);
}
bool batadv_is_ap_isolated(struct batadv_priv *bat_priv, uint8_t *src,
uint8_t *dst, unsigned short vid)
{
struct batadv_tt_local_entry *tt_local_entry = NULL;
struct batadv_tt_global_entry *tt_global_entry = NULL;
struct batadv_softif_vlan *vlan;
bool ret = false;
vlan = batadv_softif_vlan_get(bat_priv, vid);
if (!vlan || !atomic_read(&vlan->ap_isolation))
goto out;
tt_local_entry = batadv_tt_local_hash_find(bat_priv, dst, vid);
if (!tt_local_entry)
goto out;
tt_global_entry = batadv_tt_global_hash_find(bat_priv, src, vid);
if (!tt_global_entry)
goto out;
if (!_batadv_is_ap_isolated(tt_local_entry, tt_global_entry))
goto out;
ret = true;
out:
if (vlan)
batadv_softif_vlan_free_ref(vlan);
if (tt_global_entry)
batadv_tt_global_entry_free_ref(tt_global_entry);
if (tt_local_entry)
batadv_tt_local_entry_free_ref(tt_local_entry);
return ret;
}
/**
* batadv_tt_update_orig - update global translation table with new tt
* information received via ogms
* @bat_priv: the bat priv with all the soft interface information
* @orig: the orig_node of the ogm
* @tt_vlan: pointer to the first tvlv VLAN entry
* @tt_num_vlan: number of tvlv VLAN entries
* @tt_change: pointer to the first entry in the TT buffer
* @tt_num_changes: number of tt changes inside the tt buffer
* @ttvn: translation table version number of this changeset
* @tt_crc: crc32 checksum of orig node's translation table
*/
static void batadv_tt_update_orig(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node,
const void *tt_buff, uint16_t tt_num_vlan,
struct batadv_tvlv_tt_change *tt_change,
uint16_t tt_num_changes, uint8_t ttvn)
{
uint8_t orig_ttvn = (uint8_t)atomic_read(&orig_node->last_ttvn);
struct batadv_tvlv_tt_vlan_data *tt_vlan;
bool full_table = true;
tt_vlan = (struct batadv_tvlv_tt_vlan_data *)tt_buff;
/* orig table not initialised AND first diff is in the OGM OR the ttvn
* increased by one -> we can apply the attached changes
*/
if ((!orig_node->tt_initialised && ttvn == 1) ||
ttvn - orig_ttvn == 1) {
/* the OGM could not contain the changes due to their size or
* because they have already been sent BATADV_TT_OGM_APPEND_MAX
* times.
* In this case send a tt request
*/
if (!tt_num_changes) {
full_table = false;
goto request_table;
}
spin_lock_bh(&orig_node->tt_lock);
tt_change = (struct batadv_tvlv_tt_change *)tt_buff;
batadv_tt_update_changes(bat_priv, orig_node, tt_num_changes,
ttvn, tt_change);
/* Even if we received the precomputed crc with the OGM, we
* prefer to recompute it to spot any possible inconsistency
* in the global table
*/
batadv_tt_global_update_crc(bat_priv, orig_node);
spin_unlock_bh(&orig_node->tt_lock);
/* The ttvn alone is not enough to guarantee consistency
* because a single value could represent different states
* (due to the wrap around). Thus a node has to check whether
* the resulting table (after applying the changes) is still
* consistent or not. E.g. a node could disconnect while its
* ttvn is X and reconnect on ttvn = X + TTVN_MAX: in this case
* checking the CRC value is mandatory to detect the
* inconsistency
*/
if (!batadv_tt_global_check_crc(orig_node, tt_vlan,
tt_num_vlan))
goto request_table;
} else {
/* if we missed more than one change or our tables are not
* in sync anymore -> request fresh tt data
*/
if (!orig_node->tt_initialised || ttvn != orig_ttvn ||
!batadv_tt_global_check_crc(orig_node, tt_vlan,
tt_num_vlan)) {
request_table:
batadv_dbg(BATADV_DBG_TT, bat_priv,
"TT inconsistency for %pM. Need to retrieve the correct information (ttvn: %u last_ttvn: %u num_changes: %u)\n",
orig_node->orig, ttvn, orig_ttvn,
tt_num_changes);
batadv_send_tt_request(bat_priv, orig_node, ttvn,
tt_vlan, tt_num_vlan,
full_table);
return;
}
}
}
/**
* batadv_tt_global_client_is_roaming - check if a client is marked as roaming
* @bat_priv: the bat priv with all the soft interface information
* @addr: the mac address of the client to check
* @vid: VLAN identifier
*
* Returns true if we know that the client has moved from its old originator
* to another one. This entry is still kept for consistency purposes and will be
* deleted later by a DEL or because of timeout
*/
bool batadv_tt_global_client_is_roaming(struct batadv_priv *bat_priv,
uint8_t *addr, unsigned short vid)
{
struct batadv_tt_global_entry *tt_global_entry;
bool ret = false;
tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr, vid);
if (!tt_global_entry)
goto out;
ret = tt_global_entry->common.flags & BATADV_TT_CLIENT_ROAM;
batadv_tt_global_entry_free_ref(tt_global_entry);
out:
return ret;
}
/**
* batadv_tt_local_client_is_roaming - tells whether the client is roaming
* @bat_priv: the bat priv with all the soft interface information
* @addr: the mac address of the local client to query
* @vid: VLAN identifier
*
* Returns true if the local client is known to be roaming (it is not served by
* this node anymore) or not. If yes, the client is still present in the table
* to keep the latter consistent with the node TTVN
*/
bool batadv_tt_local_client_is_roaming(struct batadv_priv *bat_priv,
uint8_t *addr, unsigned short vid)
{
struct batadv_tt_local_entry *tt_local_entry;
bool ret = false;
tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr, vid);
if (!tt_local_entry)
goto out;
ret = tt_local_entry->common.flags & BATADV_TT_CLIENT_ROAM;
batadv_tt_local_entry_free_ref(tt_local_entry);
out:
return ret;
}
bool batadv_tt_add_temporary_global_entry(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig_node,
const unsigned char *addr,
unsigned short vid)
{
bool ret = false;
if (!batadv_tt_global_add(bat_priv, orig_node, addr, vid,
BATADV_TT_CLIENT_TEMP,
atomic_read(&orig_node->last_ttvn)))
goto out;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Added temporary global client (addr: %pM, vid: %d, orig: %pM)\n",
addr, BATADV_PRINT_VID(vid), orig_node->orig);
ret = true;
out:
return ret;
}
/**
* batadv_tt_tvlv_ogm_handler_v1 - process incoming tt tvlv container
* @bat_priv: the bat priv with all the soft interface information
* @orig: the orig_node of the ogm
* @flags: flags indicating the tvlv state (see batadv_tvlv_handler_flags)
* @tvlv_value: tvlv buffer containing the gateway data
* @tvlv_value_len: tvlv buffer length
*/
static void batadv_tt_tvlv_ogm_handler_v1(struct batadv_priv *bat_priv,
struct batadv_orig_node *orig,
uint8_t flags, void *tvlv_value,
uint16_t tvlv_value_len)
{
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_tvlv_tt_change *tt_change;
struct batadv_tvlv_tt_data *tt_data;
uint16_t num_entries, num_vlan;
if (tvlv_value_len < sizeof(*tt_data))
return;
tt_data = (struct batadv_tvlv_tt_data *)tvlv_value;
tvlv_value_len -= sizeof(*tt_data);
num_vlan = ntohs(tt_data->num_vlan);
if (tvlv_value_len < sizeof(*tt_vlan) * num_vlan)
return;
tt_vlan = (struct batadv_tvlv_tt_vlan_data *)(tt_data + 1);
tt_change = (struct batadv_tvlv_tt_change *)(tt_vlan + num_vlan);
tvlv_value_len -= sizeof(*tt_vlan) * num_vlan;
num_entries = batadv_tt_entries(tvlv_value_len);
batadv_tt_update_orig(bat_priv, orig, tt_vlan, num_vlan, tt_change,
num_entries, tt_data->ttvn);
}
/**
* batadv_tt_tvlv_unicast_handler_v1 - process incoming (unicast) tt tvlv
* container
* @bat_priv: the bat priv with all the soft interface information
* @src: mac address of tt tvlv sender
* @dst: mac address of tt tvlv recipient
* @tvlv_value: tvlv buffer containing the tt data
* @tvlv_value_len: tvlv buffer length
*
* Returns NET_RX_DROP if the tt tvlv is to be re-routed, NET_RX_SUCCESS
* otherwise.
*/
static int batadv_tt_tvlv_unicast_handler_v1(struct batadv_priv *bat_priv,
uint8_t *src, uint8_t *dst,
void *tvlv_value,
uint16_t tvlv_value_len)
{
struct batadv_tvlv_tt_data *tt_data;
uint16_t tt_vlan_len, tt_num_entries;
char tt_flag;
bool ret;
if (tvlv_value_len < sizeof(*tt_data))
return NET_RX_SUCCESS;
tt_data = (struct batadv_tvlv_tt_data *)tvlv_value;
tvlv_value_len -= sizeof(*tt_data);
tt_vlan_len = sizeof(struct batadv_tvlv_tt_vlan_data);
tt_vlan_len *= ntohs(tt_data->num_vlan);
if (tvlv_value_len < tt_vlan_len)
return NET_RX_SUCCESS;
tvlv_value_len -= tt_vlan_len;
tt_num_entries = batadv_tt_entries(tvlv_value_len);
switch (tt_data->flags & BATADV_TT_DATA_TYPE_MASK) {
case BATADV_TT_REQUEST:
batadv_inc_counter(bat_priv, BATADV_CNT_TT_REQUEST_RX);
/* If this node cannot provide a TT response the tt_request is
* forwarded
*/
ret = batadv_send_tt_response(bat_priv, tt_data, src, dst);
if (!ret) {
if (tt_data->flags & BATADV_TT_FULL_TABLE)
tt_flag = 'F';
else
tt_flag = '.';
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Routing TT_REQUEST to %pM [%c]\n",
dst, tt_flag);
/* tvlv API will re-route the packet */
return NET_RX_DROP;
}
break;
case BATADV_TT_RESPONSE:
batadv_inc_counter(bat_priv, BATADV_CNT_TT_RESPONSE_RX);
if (batadv_is_my_mac(bat_priv, dst)) {
batadv_handle_tt_response(bat_priv, tt_data,
src, tt_num_entries);
return NET_RX_SUCCESS;
}
if (tt_data->flags & BATADV_TT_FULL_TABLE)
tt_flag = 'F';
else
tt_flag = '.';
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Routing TT_RESPONSE to %pM [%c]\n", dst, tt_flag);
/* tvlv API will re-route the packet */
return NET_RX_DROP;
}
return NET_RX_SUCCESS;
}
/**
* batadv_roam_tvlv_unicast_handler_v1 - process incoming tt roam tvlv container
* @bat_priv: the bat priv with all the soft interface information
* @src: mac address of tt tvlv sender
* @dst: mac address of tt tvlv recipient
* @tvlv_value: tvlv buffer containing the tt data
* @tvlv_value_len: tvlv buffer length
*
* Returns NET_RX_DROP if the tt roam tvlv is to be re-routed, NET_RX_SUCCESS
* otherwise.
*/
static int batadv_roam_tvlv_unicast_handler_v1(struct batadv_priv *bat_priv,
uint8_t *src, uint8_t *dst,
void *tvlv_value,
uint16_t tvlv_value_len)
{
struct batadv_tvlv_roam_adv *roaming_adv;
struct batadv_orig_node *orig_node = NULL;
/* If this node is not the intended recipient of the
* roaming advertisement the packet is forwarded
* (the tvlv API will re-route the packet).
*/
if (!batadv_is_my_mac(bat_priv, dst))
return NET_RX_DROP;
if (tvlv_value_len < sizeof(*roaming_adv))
goto out;
orig_node = batadv_orig_hash_find(bat_priv, src);
if (!orig_node)
goto out;
batadv_inc_counter(bat_priv, BATADV_CNT_TT_ROAM_ADV_RX);
roaming_adv = (struct batadv_tvlv_roam_adv *)tvlv_value;
batadv_dbg(BATADV_DBG_TT, bat_priv,
"Received ROAMING_ADV from %pM (client %pM)\n",
src, roaming_adv->client);
batadv_tt_global_add(bat_priv, orig_node, roaming_adv->client,
ntohs(roaming_adv->vid), BATADV_TT_CLIENT_ROAM,
atomic_read(&orig_node->last_ttvn) + 1);
out:
if (orig_node)
batadv_orig_node_free_ref(orig_node);
return NET_RX_SUCCESS;
}
/**
* batadv_tt_init - initialise the translation table internals
* @bat_priv: the bat priv with all the soft interface information
*
* Return 0 on success or negative error number in case of failure.
*/
int batadv_tt_init(struct batadv_priv *bat_priv)
{
int ret;
ret = batadv_tt_local_init(bat_priv);
if (ret < 0)
return ret;
ret = batadv_tt_global_init(bat_priv);
if (ret < 0)
return ret;
batadv_tvlv_handler_register(bat_priv, batadv_tt_tvlv_ogm_handler_v1,
batadv_tt_tvlv_unicast_handler_v1,
BATADV_TVLV_TT, 1, BATADV_NO_FLAGS);
batadv_tvlv_handler_register(bat_priv, NULL,
batadv_roam_tvlv_unicast_handler_v1,
BATADV_TVLV_ROAM, 1, BATADV_NO_FLAGS);
INIT_DELAYED_WORK(&bat_priv->tt.work, batadv_tt_purge);
queue_delayed_work(batadv_event_workqueue, &bat_priv->tt.work,
msecs_to_jiffies(BATADV_TT_WORK_PERIOD));
return 1;
}