// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2008, 2009 open80211s Ltd.
* Copyright (C) 2018 - 2019 Intel Corporation
* Authors: Luis Carlos Cobo <luisca@cozybit.com>
* Javier Cardona <javier@cozybit.com>
*/
#include <linux/slab.h>
#include <asm/unaligned.h>
#include "ieee80211_i.h"
#include "mesh.h"
#include "driver-ops.h"
static int mesh_allocated;
static struct kmem_cache *rm_cache;
bool mesh_action_is_path_sel(struct ieee80211_mgmt *mgmt)
{
return (mgmt->u.action.u.mesh_action.action_code ==
WLAN_MESH_ACTION_HWMP_PATH_SELECTION);
}
void ieee80211s_init(void)
{
mesh_allocated = 1;
rm_cache = kmem_cache_create("mesh_rmc", sizeof(struct rmc_entry),
0, 0, NULL);
}
void ieee80211s_stop(void)
{
if (!mesh_allocated)
return;
kmem_cache_destroy(rm_cache);
}
static void ieee80211_mesh_housekeeping_timer(struct timer_list *t)
{
struct ieee80211_sub_if_data *sdata =
from_timer(sdata, t, u.mesh.housekeeping_timer);
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
set_bit(MESH_WORK_HOUSEKEEPING, &ifmsh->wrkq_flags);
ieee80211_queue_work(&local->hw, &sdata->work);
}
/**
* mesh_matches_local - check if the config of a mesh point matches ours
*
* @sdata: local mesh subif
* @ie: information elements of a management frame from the mesh peer
*
* This function checks if the mesh configuration of a mesh point matches the
* local mesh configuration, i.e. if both nodes belong to the same mesh network.
*/
bool mesh_matches_local(struct ieee80211_sub_if_data *sdata,
struct ieee802_11_elems *ie)
{
struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
u32 basic_rates = 0;
struct cfg80211_chan_def sta_chan_def;
struct ieee80211_supported_band *sband;
/*
* As support for each feature is added, check for matching
* - On mesh config capabilities
* - Power Save Support En
* - Sync support enabled
* - Sync support active
* - Sync support required from peer
* - MDA enabled
* - Power management control on fc
*/
if (!(ifmsh->mesh_id_len == ie->mesh_id_len &&
memcmp(ifmsh->mesh_id, ie->mesh_id, ie->mesh_id_len) == 0 &&
(ifmsh->mesh_pp_id == ie->mesh_config->meshconf_psel) &&
(ifmsh->mesh_pm_id == ie->mesh_config->meshconf_pmetric) &&
(ifmsh->mesh_cc_id == ie->mesh_config->meshconf_congest) &&
(ifmsh->mesh_sp_id == ie->mesh_config->meshconf_synch) &&
(ifmsh->mesh_auth_id == ie->mesh_config->meshconf_auth)))
return false;
sband = ieee80211_get_sband(sdata);
if (!sband)
return false;
ieee80211_sta_get_rates(sdata, ie, sband->band,
&basic_rates);
if (sdata->vif.bss_conf.basic_rates != basic_rates)
return false;
cfg80211_chandef_create(&sta_chan_def, sdata->vif.bss_conf.chandef.chan,
NL80211_CHAN_NO_HT);
ieee80211_chandef_ht_oper(ie->ht_operation, &sta_chan_def);
ieee80211_chandef_vht_oper(&sdata->local->hw,
ie->vht_operation, ie->ht_operation,
&sta_chan_def);
if (!cfg80211_chandef_compatible(&sdata->vif.bss_conf.chandef,
&sta_chan_def))
return false;
return true;
}
/**
* mesh_peer_accepts_plinks - check if an mp is willing to establish peer links
*
* @ie: information elements of a management frame from the mesh peer
*/
bool mesh_peer_accepts_plinks(struct ieee802_11_elems *ie)
{
return (ie->mesh_config->meshconf_cap &
IEEE80211_MESHCONF_CAPAB_ACCEPT_PLINKS) != 0;
}
/**
* mesh_accept_plinks_update - update accepting_plink in local mesh beacons
*
* @sdata: mesh interface in which mesh beacons are going to be updated
*
* Returns: beacon changed flag if the beacon content changed.
*/
u32 mesh_accept_plinks_update(struct ieee80211_sub_if_data *sdata)
{
bool free_plinks;
u32 changed = 0;
/* In case mesh_plink_free_count > 0 and mesh_plinktbl_capacity == 0,
* the mesh interface might be able to establish plinks with peers that
* are already on the table but are not on PLINK_ESTAB state. However,
* in general the mesh interface is not accepting peer link requests
* from new peers, and that must be reflected in the beacon
*/
free_plinks = mesh_plink_availables(sdata);
if (free_plinks != sdata->u.mesh.accepting_plinks) {
sdata->u.mesh.accepting_plinks = free_plinks;
changed = BSS_CHANGED_BEACON;
}
return changed;
}
/*
* mesh_sta_cleanup - clean up any mesh sta state
*
* @sta: mesh sta to clean up.
*/
void mesh_sta_cleanup(struct sta_info *sta)
{
struct ieee80211_sub_if_data *sdata = sta->sdata;
u32 changed = mesh_plink_deactivate(sta);
if (changed)
ieee80211_mbss_info_change_notify(sdata, changed);
}
int mesh_rmc_init(struct ieee80211_sub_if_data *sdata)
{
int i;
sdata->u.mesh.rmc = kmalloc(sizeof(struct mesh_rmc), GFP_KERNEL);
if (!sdata->u.mesh.rmc)
return -ENOMEM;
sdata->u.mesh.rmc->idx_mask = RMC_BUCKETS - 1;
for (i = 0; i < RMC_BUCKETS; i++)
INIT_HLIST_HEAD(&sdata->u.mesh.rmc->bucket[i]);
return 0;
}
void mesh_rmc_free(struct ieee80211_sub_if_data *sdata)
{
struct mesh_rmc *rmc = sdata->u.mesh.rmc;
struct rmc_entry *p;
struct hlist_node *n;
int i;
if (!sdata->u.mesh.rmc)
return;
for (i = 0; i < RMC_BUCKETS; i++) {
hlist_for_each_entry_safe(p, n, &rmc->bucket[i], list) {
hlist_del(&p->list);
kmem_cache_free(rm_cache, p);
}
}
kfree(rmc);
sdata->u.mesh.rmc = NULL;
}
/**
* mesh_rmc_check - Check frame in recent multicast cache and add if absent.
*
* @sdata: interface
* @sa: source address
* @mesh_hdr: mesh_header
*
* Returns: 0 if the frame is not in the cache, nonzero otherwise.
*
* Checks using the source address and the mesh sequence number if we have
* received this frame lately. If the frame is not in the cache, it is added to
* it.
*/
int mesh_rmc_check(struct ieee80211_sub_if_data *sdata,
const u8 *sa, struct ieee80211s_hdr *mesh_hdr)
{
struct mesh_rmc *rmc = sdata->u.mesh.rmc;
u32 seqnum = 0;
int entries = 0;
u8 idx;
struct rmc_entry *p;
struct hlist_node *n;
if (!rmc)
return -1;
/* Don't care about endianness since only match matters */
memcpy(&seqnum, &mesh_hdr->seqnum, sizeof(mesh_hdr->seqnum));
idx = le32_to_cpu(mesh_hdr->seqnum) & rmc->idx_mask;
hlist_for_each_entry_safe(p, n, &rmc->bucket[idx], list) {
++entries;
if (time_after(jiffies, p->exp_time) ||
entries == RMC_QUEUE_MAX_LEN) {
hlist_del(&p->list);
kmem_cache_free(rm_cache, p);
--entries;
} else if ((seqnum == p->seqnum) && ether_addr_equal(sa, p->sa))
return -1;
}
p = kmem_cache_alloc(rm_cache, GFP_ATOMIC);
if (!p)
return 0;
p->seqnum = seqnum;
p->exp_time = jiffies + RMC_TIMEOUT;
memcpy(p->sa, sa, ETH_ALEN);
hlist_add_head(&p->l
|