linux-stable/net/batman-adv/hard-interface.c
Jakub Kicinski c304eddcec net: wrap the wireless pointers in struct net_device in an ifdef
Most protocol-specific pointers in struct net_device are under
a respective ifdef. Wireless is the notable exception. Since
there's a sizable number of custom-built kernels for datacenter
workloads which don't build wireless it seems reasonable to
ifdefy those pointers as well.

While at it move IPv4 and IPv6 pointers up, those are special
for obvious reasons.

Acked-by: Johannes Berg <johannes@sipsolutions.net>
Acked-by: Stefan Schmidt <stefan@datenfreihafen.org> # ieee802154
Acked-by: Sven Eckelmann <sven@narfation.org>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2022-05-22 21:51:54 +01:00

1006 lines
27 KiB
C

// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) B.A.T.M.A.N. contributors:
*
* Marek Lindner, Simon Wunderlich
*/
#include "hard-interface.h"
#include "main.h"
#include <linux/atomic.h>
#include <linux/byteorder/generic.h>
#include <linux/container_of.h>
#include <linux/gfp.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/kref.h>
#include <linux/limits.h>
#include <linux/list.h>
#include <linux/minmax.h>
#include <linux/mutex.h>
#include <linux/netdevice.h>
#include <linux/printk.h>
#include <linux/rculist.h>
#include <linux/rtnetlink.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <net/net_namespace.h>
#include <net/rtnetlink.h>
#include <uapi/linux/batadv_packet.h>
#include "bat_v.h"
#include "bridge_loop_avoidance.h"
#include "distributed-arp-table.h"
#include "gateway_client.h"
#include "log.h"
#include "originator.h"
#include "send.h"
#include "soft-interface.h"
#include "translation-table.h"
/**
* batadv_hardif_release() - release hard interface from lists and queue for
* free after rcu grace period
* @ref: kref pointer of the hard interface
*/
void batadv_hardif_release(struct kref *ref)
{
struct batadv_hard_iface *hard_iface;
hard_iface = container_of(ref, struct batadv_hard_iface, refcount);
dev_put(hard_iface->net_dev);
kfree_rcu(hard_iface, rcu);
}
/**
* batadv_hardif_get_by_netdev() - Get hard interface object of a net_device
* @net_dev: net_device to search for
*
* Return: batadv_hard_iface of net_dev (with increased refcnt), NULL on errors
*/
struct batadv_hard_iface *
batadv_hardif_get_by_netdev(const struct net_device *net_dev)
{
struct batadv_hard_iface *hard_iface;
rcu_read_lock();
list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
if (hard_iface->net_dev == net_dev &&
kref_get_unless_zero(&hard_iface->refcount))
goto out;
}
hard_iface = NULL;
out:
rcu_read_unlock();
return hard_iface;
}
/**
* batadv_getlink_net() - return link net namespace (of use fallback)
* @netdev: net_device to check
* @fallback_net: return in case get_link_net is not available for @netdev
*
* Return: result of rtnl_link_ops->get_link_net or @fallback_net
*/
static struct net *batadv_getlink_net(const struct net_device *netdev,
struct net *fallback_net)
{
if (!netdev->rtnl_link_ops)
return fallback_net;
if (!netdev->rtnl_link_ops->get_link_net)
return fallback_net;
return netdev->rtnl_link_ops->get_link_net(netdev);
}
/**
* batadv_mutual_parents() - check if two devices are each others parent
* @dev1: 1st net dev
* @net1: 1st devices netns
* @dev2: 2nd net dev
* @net2: 2nd devices netns
*
* veth devices come in pairs and each is the parent of the other!
*
* Return: true if the devices are each others parent, otherwise false
*/
static bool batadv_mutual_parents(const struct net_device *dev1,
struct net *net1,
const struct net_device *dev2,
struct net *net2)
{
int dev1_parent_iflink = dev_get_iflink(dev1);
int dev2_parent_iflink = dev_get_iflink(dev2);
const struct net *dev1_parent_net;
const struct net *dev2_parent_net;
dev1_parent_net = batadv_getlink_net(dev1, net1);
dev2_parent_net = batadv_getlink_net(dev2, net2);
if (!dev1_parent_iflink || !dev2_parent_iflink)
return false;
return (dev1_parent_iflink == dev2->ifindex) &&
(dev2_parent_iflink == dev1->ifindex) &&
net_eq(dev1_parent_net, net2) &&
net_eq(dev2_parent_net, net1);
}
/**
* batadv_is_on_batman_iface() - check if a device is a batman iface descendant
* @net_dev: the device to check
*
* If the user creates any virtual device on top of a batman-adv interface, it
* is important to prevent this new interface from being used to create a new
* mesh network (this behaviour would lead to a batman-over-batman
* configuration). This function recursively checks all the fathers of the
* device passed as argument looking for a batman-adv soft interface.
*
* Return: true if the device is descendant of a batman-adv mesh interface (or
* if it is a batman-adv interface itself), false otherwise
*/
static bool batadv_is_on_batman_iface(const struct net_device *net_dev)
{
struct net *net = dev_net(net_dev);
struct net_device *parent_dev;
struct net *parent_net;
int iflink;
bool ret;
/* check if this is a batman-adv mesh interface */
if (batadv_softif_is_valid(net_dev))
return true;
iflink = dev_get_iflink(net_dev);
if (iflink == 0)
return false;
parent_net = batadv_getlink_net(net_dev, net);
/* iflink to itself, most likely physical device */
if (net == parent_net && iflink == net_dev->ifindex)
return false;
/* recurse over the parent device */
parent_dev = __dev_get_by_index((struct net *)parent_net, iflink);
if (!parent_dev) {
pr_warn("Cannot find parent device. Skipping batadv-on-batadv check for %s\n",
net_dev->name);
return false;
}
if (batadv_mutual_parents(net_dev, net, parent_dev, parent_net))
return false;
ret = batadv_is_on_batman_iface(parent_dev);
return ret;
}
static bool batadv_is_valid_iface(const struct net_device *net_dev)
{
if (net_dev->flags & IFF_LOOPBACK)
return false;
if (net_dev->type != ARPHRD_ETHER)
return false;
if (net_dev->addr_len != ETH_ALEN)
return false;
/* no batman over batman */
if (batadv_is_on_batman_iface(net_dev))
return false;
return true;
}
/**
* batadv_get_real_netdevice() - check if the given netdev struct is a virtual
* interface on top of another 'real' interface
* @netdev: the device to check
*
* Callers must hold the rtnl semaphore. You may want batadv_get_real_netdev()
* instead of this.
*
* Return: the 'real' net device or the original net device and NULL in case
* of an error.
*/
static struct net_device *batadv_get_real_netdevice(struct net_device *netdev)
{
struct batadv_hard_iface *hard_iface = NULL;
struct net_device *real_netdev = NULL;
struct net *real_net;
struct net *net;
int iflink;
ASSERT_RTNL();
if (!netdev)
return NULL;
iflink = dev_get_iflink(netdev);
if (iflink == 0) {
dev_hold(netdev);
return netdev;
}
hard_iface = batadv_hardif_get_by_netdev(netdev);
if (!hard_iface || !hard_iface->soft_iface)
goto out;
net = dev_net(hard_iface->soft_iface);
real_net = batadv_getlink_net(netdev, net);
/* iflink to itself, most likely physical device */
if (net == real_net && netdev->ifindex == iflink) {
real_netdev = netdev;
dev_hold(real_netdev);
goto out;
}
real_netdev = dev_get_by_index(real_net, iflink);
out:
batadv_hardif_put(hard_iface);
return real_netdev;
}
/**
* batadv_get_real_netdev() - check if the given net_device struct is a virtual
* interface on top of another 'real' interface
* @net_device: the device to check
*
* Return: the 'real' net device or the original net device and NULL in case
* of an error.
*/
struct net_device *batadv_get_real_netdev(struct net_device *net_device)
{
struct net_device *real_netdev;
rtnl_lock();
real_netdev = batadv_get_real_netdevice(net_device);
rtnl_unlock();
return real_netdev;
}
/**
* batadv_is_wext_netdev() - check if the given net_device struct is a
* wext wifi interface
* @net_device: the device to check
*
* Return: true if the net device is a wext wireless device, false
* otherwise.
*/
static bool batadv_is_wext_netdev(struct net_device *net_device)
{
if (!net_device)
return false;
#ifdef CONFIG_WIRELESS_EXT
/* pre-cfg80211 drivers have to implement WEXT, so it is possible to
* check for wireless_handlers != NULL
*/
if (net_device->wireless_handlers)
return true;
#endif
return false;
}
/**
* batadv_is_cfg80211_netdev() - check if the given net_device struct is a
* cfg80211 wifi interface
* @net_device: the device to check
*
* Return: true if the net device is a cfg80211 wireless device, false
* otherwise.
*/
static bool batadv_is_cfg80211_netdev(struct net_device *net_device)
{
if (!net_device)
return false;
#if IS_ENABLED(CONFIG_CFG80211)
/* cfg80211 drivers have to set ieee80211_ptr */
if (net_device->ieee80211_ptr)
return true;
#endif
return false;
}
/**
* batadv_wifi_flags_evaluate() - calculate wifi flags for net_device
* @net_device: the device to check
*
* Return: batadv_hard_iface_wifi_flags flags of the device
*/
static u32 batadv_wifi_flags_evaluate(struct net_device *net_device)
{
u32 wifi_flags = 0;
struct net_device *real_netdev;
if (batadv_is_wext_netdev(net_device))
wifi_flags |= BATADV_HARDIF_WIFI_WEXT_DIRECT;
if (batadv_is_cfg80211_netdev(net_device))
wifi_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
real_netdev = batadv_get_real_netdevice(net_device);
if (!real_netdev)
return wifi_flags;
if (real_netdev == net_device)
goto out;
if (batadv_is_wext_netdev(real_netdev))
wifi_flags |= BATADV_HARDIF_WIFI_WEXT_INDIRECT;
if (batadv_is_cfg80211_netdev(real_netdev))
wifi_flags |= BATADV_HARDIF_WIFI_CFG80211_INDIRECT;
out:
dev_put(real_netdev);
return wifi_flags;
}
/**
* batadv_is_cfg80211_hardif() - check if the given hardif is a cfg80211 wifi
* interface
* @hard_iface: the device to check
*
* Return: true if the net device is a cfg80211 wireless device, false
* otherwise.
*/
bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface)
{
u32 allowed_flags = 0;
allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
allowed_flags |= BATADV_HARDIF_WIFI_CFG80211_INDIRECT;
return !!(hard_iface->wifi_flags & allowed_flags);
}
/**
* batadv_is_wifi_hardif() - check if the given hardif is a wifi interface
* @hard_iface: the device to check
*
* Return: true if the net device is a 802.11 wireless device, false otherwise.
*/
bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface)
{
if (!hard_iface)
return false;
return hard_iface->wifi_flags != 0;
}
/**
* batadv_hardif_no_broadcast() - check whether (re)broadcast is necessary
* @if_outgoing: the outgoing interface checked and considered for (re)broadcast
* @orig_addr: the originator of this packet
* @orig_neigh: originator address of the forwarder we just got the packet from
* (NULL if we originated)
*
* Checks whether a packet needs to be (re)broadcasted on the given interface.
*
* Return:
* BATADV_HARDIF_BCAST_NORECIPIENT: No neighbor on interface
* BATADV_HARDIF_BCAST_DUPFWD: Just one neighbor, but it is the forwarder
* BATADV_HARDIF_BCAST_DUPORIG: Just one neighbor, but it is the originator
* BATADV_HARDIF_BCAST_OK: Several neighbors, must broadcast
*/
int batadv_hardif_no_broadcast(struct batadv_hard_iface *if_outgoing,
u8 *orig_addr, u8 *orig_neigh)
{
struct batadv_hardif_neigh_node *hardif_neigh;
struct hlist_node *first;
int ret = BATADV_HARDIF_BCAST_OK;
rcu_read_lock();
/* 0 neighbors -> no (re)broadcast */
first = rcu_dereference(hlist_first_rcu(&if_outgoing->neigh_list));
if (!first) {
ret = BATADV_HARDIF_BCAST_NORECIPIENT;
goto out;
}
/* >1 neighbors -> (re)broadcast */
if (rcu_dereference(hlist_next_rcu(first)))
goto out;
hardif_neigh = hlist_entry(first, struct batadv_hardif_neigh_node,
list);
/* 1 neighbor, is the originator -> no rebroadcast */
if (orig_addr && batadv_compare_eth(hardif_neigh->orig, orig_addr)) {
ret = BATADV_HARDIF_BCAST_DUPORIG;
/* 1 neighbor, is the one we received from -> no rebroadcast */
} else if (orig_neigh &&
batadv_compare_eth(hardif_neigh->orig, orig_neigh)) {
ret = BATADV_HARDIF_BCAST_DUPFWD;
}
out:
rcu_read_unlock();
return ret;
}
static struct batadv_hard_iface *
batadv_hardif_get_active(const struct net_device *soft_iface)
{
struct batadv_hard_iface *hard_iface;
rcu_read_lock();
list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
if (hard_iface->soft_iface != soft_iface)
continue;
if (hard_iface->if_status == BATADV_IF_ACTIVE &&
kref_get_unless_zero(&hard_iface->refcount))
goto out;
}
hard_iface = NULL;
out:
rcu_read_unlock();
return hard_iface;
}
static void batadv_primary_if_update_addr(struct batadv_priv *bat_priv,
struct batadv_hard_iface *oldif)
{
struct batadv_hard_iface *primary_if;
primary_if = batadv_primary_if_get_selected(bat_priv);
if (!primary_if)
goto out;
batadv_dat_init_own_addr(bat_priv, primary_if);
batadv_bla_update_orig_address(bat_priv, primary_if, oldif);
out:
batadv_hardif_put(primary_if);
}
static void batadv_primary_if_select(struct batadv_priv *bat_priv,
struct batadv_hard_iface *new_hard_iface)
{
struct batadv_hard_iface *curr_hard_iface;
ASSERT_RTNL();
if (new_hard_iface)
kref_get(&new_hard_iface->refcount);
curr_hard_iface = rcu_replace_pointer(bat_priv->primary_if,
new_hard_iface, 1);
if (!new_hard_iface)
goto out;
bat_priv->algo_ops->iface.primary_set(new_hard_iface);
batadv_primary_if_update_addr(bat_priv, curr_hard_iface);
out:
batadv_hardif_put(curr_hard_iface);
}
static bool
batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface)
{
if (hard_iface->net_dev->flags & IFF_UP)
return true;
return false;
}
static void batadv_check_known_mac_addr(const struct net_device *net_dev)
{
const struct batadv_hard_iface *hard_iface;
rcu_read_lock();
list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
if (hard_iface->if_status != BATADV_IF_ACTIVE &&
hard_iface->if_status != BATADV_IF_TO_BE_ACTIVATED)
continue;
if (hard_iface->net_dev == net_dev)
continue;
if (!batadv_compare_eth(hard_iface->net_dev->dev_addr,
net_dev->dev_addr))
continue;
pr_warn("The newly added mac address (%pM) already exists on: %s\n",
net_dev->dev_addr, hard_iface->net_dev->name);
pr_warn("It is strongly recommended to keep mac addresses unique to avoid problems!\n");
}
rcu_read_unlock();
}
/**
* batadv_hardif_recalc_extra_skbroom() - Recalculate skbuff extra head/tailroom
* @soft_iface: netdev struct of the mesh interface
*/
static void batadv_hardif_recalc_extra_skbroom(struct net_device *soft_iface)
{
const struct batadv_hard_iface *hard_iface;
unsigned short lower_header_len = ETH_HLEN;
unsigned short lower_headroom = 0;
unsigned short lower_tailroom = 0;
unsigned short needed_headroom;
rcu_read_lock();
list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
if (hard_iface->if_status == BATADV_IF_NOT_IN_USE)
continue;
if (hard_iface->soft_iface != soft_iface)
continue;
lower_header_len = max_t(unsigned short, lower_header_len,
hard_iface->net_dev->hard_header_len);
lower_headroom = max_t(unsigned short, lower_headroom,
hard_iface->net_dev->needed_headroom);
lower_tailroom = max_t(unsigned short, lower_tailroom,
hard_iface->net_dev->needed_tailroom);
}
rcu_read_unlock();
needed_headroom = lower_headroom + (lower_header_len - ETH_HLEN);
needed_headroom += batadv_max_header_len();
/* fragmentation headers don't strip the unicast/... header */
needed_headroom += sizeof(struct batadv_frag_packet);
soft_iface->needed_headroom = needed_headroom;
soft_iface->needed_tailroom = lower_tailroom;
}
/**
* batadv_hardif_min_mtu() - Calculate maximum MTU for soft interface
* @soft_iface: netdev struct of the soft interface
*
* Return: MTU for the soft-interface (limited by the minimal MTU of all active
* slave interfaces)
*/
int batadv_hardif_min_mtu(struct net_device *soft_iface)
{
struct batadv_priv *bat_priv = netdev_priv(soft_iface);
const struct batadv_hard_iface *hard_iface;
int min_mtu = INT_MAX;
rcu_read_lock();
list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
if (hard_iface->if_status != BATADV_IF_ACTIVE &&
hard_iface->if_status != BATADV_IF_TO_BE_ACTIVATED)
continue;
if (hard_iface->soft_iface != soft_iface)
continue;
min_mtu = min_t(int, hard_iface->net_dev->mtu, min_mtu);
}
rcu_read_unlock();
if (atomic_read(&bat_priv->fragmentation) == 0)
goto out;
/* with fragmentation enabled the maximum size of internally generated
* packets such as translation table exchanges or tvlv containers, etc
* has to be calculated
*/
min_mtu = min_t(int, min_mtu, BATADV_FRAG_MAX_FRAG_SIZE);
min_mtu -= sizeof(struct batadv_frag_packet);
min_mtu *= BATADV_FRAG_MAX_FRAGMENTS;
out:
/* report to the other components the maximum amount of bytes that
* batman-adv can send over the wire (without considering the payload
* overhead). For example, this value is used by TT to compute the
* maximum local table size
*/
atomic_set(&bat_priv->packet_size_max, min_mtu);
/* the real soft-interface MTU is computed by removing the payload
* overhead from the maximum amount of bytes that was just computed.
*
* However batman-adv does not support MTUs bigger than ETH_DATA_LEN
*/
return min_t(int, min_mtu - batadv_max_header_len(), ETH_DATA_LEN);
}
/**
* batadv_update_min_mtu() - Adjusts the MTU if a new interface with a smaller
* MTU appeared
* @soft_iface: netdev struct of the soft interface
*/
void batadv_update_min_mtu(struct net_device *soft_iface)
{
soft_iface->mtu = batadv_hardif_min_mtu(soft_iface);
/* Check if the local translate table should be cleaned up to match a
* new (and smaller) MTU.
*/
batadv_tt_local_resize_to_mtu(soft_iface);
}
static void
batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface)
{
struct batadv_priv *bat_priv;
struct batadv_hard_iface *primary_if = NULL;
if (hard_iface->if_status != BATADV_IF_INACTIVE)
goto out;
bat_priv = netdev_priv(hard_iface->soft_iface);
bat_priv->algo_ops->iface.update_mac(hard_iface);
hard_iface->if_status = BATADV_IF_TO_BE_ACTIVATED;
/* the first active interface becomes our primary interface or
* the next active interface after the old primary interface was removed
*/
primary_if = batadv_primary_if_get_selected(bat_priv);
if (!primary_if)
batadv_primary_if_select(bat_priv, hard_iface);
batadv_info(hard_iface->soft_iface, "Interface activated: %s\n",
hard_iface->net_dev->name);
batadv_update_min_mtu(hard_iface->soft_iface);
if (bat_priv->algo_ops->iface.activate)
bat_priv->algo_ops->iface.activate(hard_iface);
out:
batadv_hardif_put(primary_if);
}
static void
batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface)
{
if (hard_iface->if_status != BATADV_IF_ACTIVE &&
hard_iface->if_status != BATADV_IF_TO_BE_ACTIVATED)
return;
hard_iface->if_status = BATADV_IF_INACTIVE;
batadv_info(hard_iface->soft_iface, "Interface deactivated: %s\n",
hard_iface->net_dev->name);
batadv_update_min_mtu(hard_iface->soft_iface);
}
/**
* batadv_hardif_enable_interface() - Enslave hard interface to soft interface
* @hard_iface: hard interface to add to soft interface
* @soft_iface: netdev struct of the mesh interface
*
* Return: 0 on success or negative error number in case of failure
*/
int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface,
struct net_device *soft_iface)
{
struct batadv_priv *bat_priv;
__be16 ethertype = htons(ETH_P_BATMAN);
int max_header_len = batadv_max_header_len();
int ret;
if (hard_iface->if_status != BATADV_IF_NOT_IN_USE)
goto out;
kref_get(&hard_iface->refcount);
dev_hold(soft_iface);
hard_iface->soft_iface = soft_iface;
bat_priv = netdev_priv(hard_iface->soft_iface);
ret = netdev_master_upper_dev_link(hard_iface->net_dev,
soft_iface, NULL, NULL, NULL);
if (ret)
goto err_dev;
ret = bat_priv->algo_ops->iface.enable(hard_iface);
if (ret < 0)
goto err_upper;
hard_iface->if_status = BATADV_IF_INACTIVE;
kref_get(&hard_iface->refcount);
hard_iface->batman_adv_ptype.type = ethertype;
hard_iface->batman_adv_ptype.func = batadv_batman_skb_recv;
hard_iface->batman_adv_ptype.dev = hard_iface->net_dev;
dev_add_pack(&hard_iface->batman_adv_ptype);
batadv_info(hard_iface->soft_iface, "Adding interface: %s\n",
hard_iface->net_dev->name);
if (atomic_read(&bat_priv->fragmentation) &&
hard_iface->net_dev->mtu < ETH_DATA_LEN + max_header_len)
batadv_info(hard_iface->soft_iface,
"The MTU of interface %s is too small (%i) to handle the transport of batman-adv packets. Packets going over this interface will be fragmented on layer2 which could impact the performance. Setting the MTU to %i would solve the problem.\n",
hard_iface->net_dev->name, hard_iface->net_dev->mtu,
ETH_DATA_LEN + max_header_len);
if (!atomic_read(&bat_priv->fragmentation) &&
hard_iface->net_dev->mtu < ETH_DATA_LEN + max_header_len)
batadv_info(hard_iface->soft_iface,
"The MTU of interface %s is too small (%i) to handle the transport of batman-adv packets. If you experience problems getting traffic through try increasing the MTU to %i.\n",
hard_iface->net_dev->name, hard_iface->net_dev->mtu,
ETH_DATA_LEN + max_header_len);
if (batadv_hardif_is_iface_up(hard_iface))
batadv_hardif_activate_interface(hard_iface);
else
batadv_err(hard_iface->soft_iface,
"Not using interface %s (retrying later): interface not active\n",
hard_iface->net_dev->name);
batadv_hardif_recalc_extra_skbroom(soft_iface);
if (bat_priv->algo_ops->iface.enabled)
bat_priv->algo_ops->iface.enabled(hard_iface);
out:
return 0;
err_upper:
netdev_upper_dev_unlink(hard_iface->net_dev, soft_iface);
err_dev:
hard_iface->soft_iface = NULL;
dev_put(soft_iface);
batadv_hardif_put(hard_iface);
return ret;
}
/**
* batadv_hardif_cnt() - get number of interfaces enslaved to soft interface
* @soft_iface: soft interface to check
*
* This function is only using RCU for locking - the result can therefore be
* off when another function is modifying the list at the same time. The
* caller can use the rtnl_lock to make sure that the count is accurate.
*
* Return: number of connected/enslaved hard interfaces
*/
static size_t batadv_hardif_cnt(const struct net_device *soft_iface)
{
struct batadv_hard_iface *hard_iface;
size_t count = 0;
rcu_read_lock();
list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) {
if (hard_iface->soft_iface != soft_iface)
continue;
count++;
}
rcu_read_unlock();
return count;
}
/**
* batadv_hardif_disable_interface() - Remove hard interface from soft interface
* @hard_iface: hard interface to be removed
*/
void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface)
{
struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
struct batadv_hard_iface *primary_if = NULL;
batadv_hardif_deactivate_interface(hard_iface);
if (hard_iface->if_status != BATADV_IF_INACTIVE)
goto out;
batadv_info(hard_iface->soft_iface, "Removing interface: %s\n",
hard_iface->net_dev->name);
dev_remove_pack(&hard_iface->batman_adv_ptype);
batadv_hardif_put(hard_iface);
primary_if = batadv_primary_if_get_selected(bat_priv);
if (hard_iface == primary_if) {
struct batadv_hard_iface *new_if;
new_if = batadv_hardif_get_active(hard_iface->soft_iface);
batadv_primary_if_select(bat_priv, new_if);
batadv_hardif_put(new_if);
}
bat_priv->algo_ops->iface.disable(hard_iface);
hard_iface->if_status = BATADV_IF_NOT_IN_USE;
/* delete all references to this hard_iface */
batadv_purge_orig_ref(bat_priv);
batadv_purge_outstanding_packets(bat_priv, hard_iface);
dev_put(hard_iface->soft_iface);
netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->soft_iface);
batadv_hardif_recalc_extra_skbroom(hard_iface->soft_iface);
/* nobody uses this interface anymore */
if (batadv_hardif_cnt(hard_iface->soft_iface) <= 1)
batadv_gw_check_client_stop(bat_priv);
hard_iface->soft_iface = NULL;
batadv_hardif_put(hard_iface);
out:
batadv_hardif_put(primary_if);
}
static struct batadv_hard_iface *
batadv_hardif_add_interface(struct net_device *net_dev)
{
struct batadv_hard_iface *hard_iface;
ASSERT_RTNL();
if (!batadv_is_valid_iface(net_dev))
goto out;
dev_hold(net_dev);
hard_iface = kzalloc(sizeof(*hard_iface), GFP_ATOMIC);
if (!hard_iface)
goto release_dev;
hard_iface->net_dev = net_dev;
hard_iface->soft_iface = NULL;
hard_iface->if_status = BATADV_IF_NOT_IN_USE;
INIT_LIST_HEAD(&hard_iface->list);
INIT_HLIST_HEAD(&hard_iface->neigh_list);
mutex_init(&hard_iface->bat_iv.ogm_buff_mutex);
spin_lock_init(&hard_iface->neigh_list_lock);
kref_init(&hard_iface->refcount);
hard_iface->num_bcasts = BATADV_NUM_BCASTS_DEFAULT;
hard_iface->wifi_flags = batadv_wifi_flags_evaluate(net_dev);
if (batadv_is_wifi_hardif(hard_iface))
hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
atomic_set(&hard_iface->hop_penalty, 0);
batadv_v_hardif_init(hard_iface);
batadv_check_known_mac_addr(hard_iface->net_dev);
kref_get(&hard_iface->refcount);
list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list);
batadv_hardif_generation++;
return hard_iface;
release_dev:
dev_put(net_dev);
out:
return NULL;
}
static void batadv_hardif_remove_interface(struct batadv_hard_iface *hard_iface)
{
ASSERT_RTNL();
/* first deactivate interface */
if (hard_iface->if_status != BATADV_IF_NOT_IN_USE)
batadv_hardif_disable_interface(hard_iface);
if (hard_iface->if_status != BATADV_IF_NOT_IN_USE)
return;
hard_iface->if_status = BATADV_IF_TO_BE_REMOVED;
batadv_hardif_put(hard_iface);
}
/**
* batadv_hard_if_event_softif() - Handle events for soft interfaces
* @event: NETDEV_* event to handle
* @net_dev: net_device which generated an event
*
* Return: NOTIFY_* result
*/
static int batadv_hard_if_event_softif(unsigned long event,
struct net_device *net_dev)
{
struct batadv_priv *bat_priv;
switch (event) {
case NETDEV_REGISTER:
bat_priv = netdev_priv(net_dev);
batadv_softif_create_vlan(bat_priv, BATADV_NO_FLAGS);
break;
}
return NOTIFY_DONE;
}
static int batadv_hard_if_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
struct net_device *net_dev = netdev_notifier_info_to_dev(ptr);
struct batadv_hard_iface *hard_iface;
struct batadv_hard_iface *primary_if = NULL;
struct batadv_priv *bat_priv;
if (batadv_softif_is_valid(net_dev))
return batadv_hard_if_event_softif(event, net_dev);
hard_iface = batadv_hardif_get_by_netdev(net_dev);
if (!hard_iface && (event == NETDEV_REGISTER ||
event == NETDEV_POST_TYPE_CHANGE))
hard_iface = batadv_hardif_add_interface(net_dev);
if (!hard_iface)
goto out;
switch (event) {
case NETDEV_UP:
batadv_hardif_activate_interface(hard_iface);
break;
case NETDEV_GOING_DOWN:
case NETDEV_DOWN:
batadv_hardif_deactivate_interface(hard_iface);
break;
case NETDEV_UNREGISTER:
case NETDEV_PRE_TYPE_CHANGE:
list_del_rcu(&hard_iface->list);
batadv_hardif_generation++;
batadv_hardif_remove_interface(hard_iface);
break;
case NETDEV_CHANGEMTU:
if (hard_iface->soft_iface)
batadv_update_min_mtu(hard_iface->soft_iface);
break;
case NETDEV_CHANGEADDR:
if (hard_iface->if_status == BATADV_IF_NOT_IN_USE)
goto hardif_put;
batadv_check_known_mac_addr(hard_iface->net_dev);
bat_priv = netdev_priv(hard_iface->soft_iface);
bat_priv->algo_ops->iface.update_mac(hard_iface);
primary_if = batadv_primary_if_get_selected(bat_priv);
if (!primary_if)
goto hardif_put;
if (hard_iface == primary_if)
batadv_primary_if_update_addr(bat_priv, NULL);
break;
case NETDEV_CHANGEUPPER:
hard_iface->wifi_flags = batadv_wifi_flags_evaluate(net_dev);
if (batadv_is_wifi_hardif(hard_iface))
hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
break;
default:
break;
}
hardif_put:
batadv_hardif_put(hard_iface);
out:
batadv_hardif_put(primary_if);
return NOTIFY_DONE;
}
struct notifier_block batadv_hard_if_notifier = {
.notifier_call = batadv_hard_if_event,
};