summaryrefslogtreecommitdiffstats
path: root/net/batman-adv/hard-interface.c
diff options
context:
space:
mode:
authorSteven Rostedt <rostedt@goodmis.org>2026-09-02 09:55:01 -0400
committerSteven Rostedt <rostedt@goodmis.org>2026-09-02 11:08:21 -0400
commit4617721c502b2ddaa4e324e86da4997edf738fa5 (patch)
tree350aa83b47da0b574a814795392b20b0bed83cbc /net/batman-adv/hard-interface.c
downloadlinux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz
linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'net/batman-adv/hard-interface.c')
-rw-r--r--net/batman-adv/hard-interface.c1192
1 files changed, 1192 insertions, 0 deletions
diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c
new file mode 100644
index 000000000..e7ad29550
--- /dev/null
+++ b/net/batman-adv/hard-interface.c
@@ -0,0 +1,1192 @@
+// 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/bug.h>
+#include <linux/byteorder/generic.h>
+#include <linux/compiler.h>
+#include <linux/container_of.h>
+#include <linux/errno.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/notifier.h>
+#include <linux/printk.h>
+#include <linux/rculist.h>
+#include <linux/rhashtable-types.h>
+#include <linux/rhashtable.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 "mesh-interface.h"
+#include "originator.h"
+#include "send.h"
+#include "translation-table.h"
+
+static const struct rhashtable_params batadv_wifi_net_devices_params = {
+ .key_len = sizeof(struct net_device *),
+ .key_offset = offsetof(struct batadv_wifi_net_device_state, netdev),
+ .head_offset = offsetof(struct batadv_wifi_net_device_state, l),
+ .automatic_shrinking = true,
+};
+
+static struct rhashtable batadv_wifi_net_devices;
+
+/**
+ * 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);
+ netdev_put(hard_iface->mesh_iface, &hard_iface->meshif_dev_tracker);
+ netdev_put(hard_iface->net_dev, &hard_iface->dev_tracker);
+
+ 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(struct net_device *net_dev)
+{
+ struct batadv_hard_iface *hard_iface;
+ struct net_device *mesh_iface;
+
+ ASSERT_RTNL();
+
+ mesh_iface = netdev_master_upper_dev_get(net_dev);
+ if (!mesh_iface || !batadv_meshif_is_valid(mesh_iface))
+ return NULL;
+
+ hard_iface = netdev_lower_dev_get_private(mesh_iface, net_dev);
+ if (!kref_get_unless_zero(&hard_iface->refcount))
+ return NULL;
+
+ 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 mesh 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_meshif_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;
+}
+
+/**
+ * batadv_is_valid_iface() - check whether a net_device can be used as a hard
+ * interface
+ * @net_dev: the net_device to check
+ *
+ * Refuse loopback devices, non-Ethernet devices, devices with a non-Ethernet
+ * address length and any device that is already part of a batman-adv mesh
+ * interface stack.
+ *
+ * Return: true if @net_dev can be used as a hard interface, false otherwise
+ */
+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_netdev() - 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.
+ */
+struct net_device *__batadv_get_real_netdev(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)
+ goto out;
+
+ net = dev_net(hard_iface->mesh_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_netdev(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)
+{
+ struct net_device *real_netdev;
+ u32 wifi_flags = 0;
+
+ 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_netdev(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_netdev_get_wifi_flags() - retrieve wifi flags for net_device
+ * @net_dev: the device to check
+ *
+ * Return: batadv_hard_iface_wifi_flags flags of the device
+ */
+u32 batadv_netdev_get_wifi_flags(struct net_device *net_dev)
+{
+ struct batadv_wifi_net_device_state *device_state;
+ u32 wifi_flags = 0;
+
+ rcu_read_lock();
+ device_state = rhashtable_lookup_fast(&batadv_wifi_net_devices,
+ &net_dev,
+ batadv_wifi_net_devices_params);
+ if (device_state)
+ wifi_flags = READ_ONCE(device_state->wifi_flags);
+ rcu_read_unlock();
+
+ return wifi_flags;
+}
+
+/**
+ * batadv_hardif_get_wifi_flags() - retrieve wifi flags for hard_iface
+ * @hard_iface: the device to check
+ *
+ * Return: batadv_hard_iface_wifi_flags flags of the device
+ */
+u32 batadv_hardif_get_wifi_flags(struct batadv_hard_iface *hard_iface)
+{
+ if (!hard_iface)
+ return 0;
+
+ return batadv_netdev_get_wifi_flags(hard_iface->net_dev);
+}
+
+/**
+ * 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)
+{
+ u32 wifi_flags = batadv_hardif_get_wifi_flags(hard_iface);
+
+ return batadv_is_wifi(wifi_flags);
+}
+
+/**
+ * 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;
+ int ret = BATADV_HARDIF_BCAST_OK;
+ struct hlist_node *first;
+
+ 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;
+}
+
+/**
+ * batadv_hardif_get_active() - retrieve an active hard interface for a mesh
+ * interface
+ * @mesh_iface: mesh interface to search
+ *
+ * Return: first hard interface in BATADV_IF_ACTIVE state attached to
+ * @mesh_iface, or NULL if none is active.
+ */
+static struct batadv_hard_iface *
+batadv_hardif_get_active(struct net_device *mesh_iface)
+{
+ struct batadv_hard_iface *hard_iface;
+ struct list_head *iter;
+
+ rcu_read_lock();
+ netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) {
+ 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;
+}
+
+/**
+ * batadv_primary_if_update_addr() - propagate the new primary interface MAC
+ * address to all dependent components
+ * @bat_priv: the bat priv with all the mesh interface information
+ * @oldif: previously used primary interface, or NULL if none
+ *
+ * Inform DAT and BLA that the originator address has changed so they can
+ * adjust their state accordingly.
+ */
+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);
+}
+
+/**
+ * batadv_primary_if_select() - select the new primary interface
+ * @bat_priv: the bat priv with all the mesh interface information
+ * @new_hard_iface: new primary interface, may be NULL
+ *
+ * Replace the currently selected primary interface with @new_hard_iface,
+ * invoke the algorithm-specific primary_set hook and update the originator
+ * MAC address.
+ */
+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);
+}
+
+/**
+ * batadv_hardif_is_iface_up() - check whether the underlying net_device of a
+ * hard interface is up
+ * @hard_iface: the hard interface to check
+ *
+ * Return: true if the underlying net_device has the IFF_UP flag set
+ */
+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;
+}
+
+/**
+ * batadv_check_known_mac_addr() - warn about duplicate hard interface MAC
+ * addresses
+ * @hard_iface: hard interface that was just added or had its MAC changed
+ *
+ * Iterate over all hard interfaces of the same mesh interface and emit a
+ * warning if another in-use interface shares the same MAC address as
+ * @hard_iface.
+ */
+static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_iface)
+{
+ struct net_device *mesh_iface = hard_iface->mesh_iface;
+ const struct batadv_hard_iface *tmp_hard_iface;
+ struct list_head *iter;
+
+ netdev_for_each_lower_private(mesh_iface, tmp_hard_iface, iter) {
+ if (tmp_hard_iface == hard_iface)
+ continue;
+
+ if (!batadv_compare_eth(tmp_hard_iface->net_dev->dev_addr,
+ hard_iface->net_dev->dev_addr))
+ continue;
+
+ pr_warn("The newly added mac address (%pM) already exists on: %s\n",
+ hard_iface->net_dev->dev_addr, tmp_hard_iface->net_dev->name);
+ pr_warn("It is strongly recommended to keep mac addresses unique to avoid problems!\n");
+ }
+}
+
+/**
+ * batadv_hardif_recalc_extra_skbroom() - Recalculate skbuff extra head/tailroom
+ * @mesh_iface: netdev struct of the mesh interface
+ */
+static void batadv_hardif_recalc_extra_skbroom(struct net_device *mesh_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;
+ struct list_head *iter;
+
+ rcu_read_lock();
+ netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) {
+ 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);
+
+ mesh_iface->needed_headroom = needed_headroom;
+ mesh_iface->needed_tailroom = lower_tailroom;
+}
+
+/**
+ * batadv_hardif_min_mtu() - Calculate maximum MTU for mesh interface
+ * @mesh_iface: netdev struct of the mesh interface
+ *
+ * Return: MTU for the mesh-interface (limited by the minimal MTU of all active
+ * slave interfaces)
+ */
+int batadv_hardif_min_mtu(struct net_device *mesh_iface)
+{
+ struct batadv_priv *bat_priv = netdev_priv(mesh_iface);
+ const struct batadv_hard_iface *hard_iface;
+ struct list_head *iter;
+ int min_mtu = INT_MAX;
+
+ rcu_read_lock();
+ netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter) {
+ if (hard_iface->if_status != BATADV_IF_ACTIVE &&
+ hard_iface->if_status != BATADV_IF_TO_BE_ACTIVATED)
+ continue;
+
+ min_mtu = min_t(int, hard_iface->net_dev->mtu, min_mtu);
+ }
+ rcu_read_unlock();
+
+ if (READ_ONCE(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
+ */
+ WRITE_ONCE(bat_priv->packet_size_max, min_mtu);
+
+ /* the real mesh-interface MTU is computed by removing the payload
+ * overhead from the maximum amount of bytes that was just computed.
+ */
+ return min_t(int, min_mtu - batadv_max_header_len(), BATADV_MAX_MTU);
+}
+
+/**
+ * batadv_update_min_mtu() - Adjusts the MTU if a new interface with a smaller
+ * MTU appeared
+ * @mesh_iface: netdev struct of the mesh interface
+ */
+void batadv_update_min_mtu(struct net_device *mesh_iface)
+{
+ struct batadv_priv *bat_priv = netdev_priv(mesh_iface);
+ int limit_mtu;
+ int mtu;
+
+ mtu = batadv_hardif_min_mtu(mesh_iface);
+
+ if (bat_priv->mtu_set_by_user)
+ limit_mtu = bat_priv->mtu_set_by_user;
+ else
+ limit_mtu = ETH_DATA_LEN;
+
+ mtu = min(mtu, limit_mtu);
+ dev_set_mtu(mesh_iface, mtu);
+
+ /* Check if the local translate table should be cleaned up to match a
+ * new (and smaller) MTU.
+ */
+ batadv_tt_local_resize_to_mtu(mesh_iface);
+}
+
+/**
+ * batadv_hardif_activate_interface() - move a hard interface to the active
+ * state
+ * @hard_iface: the interface that has come up
+ *
+ * Activate an interface, select it as the primary interface if no
+ * primary is currently active and invoke the algorithm-specific
+ * activate hook.
+ */
+static void
+batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface)
+{
+ struct batadv_hard_iface *primary_if = NULL;
+ struct batadv_priv *bat_priv;
+
+ if (hard_iface->if_status != BATADV_IF_INACTIVE)
+ goto out;
+
+ bat_priv = netdev_priv(hard_iface->mesh_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->mesh_iface, "Interface activated: %s\n",
+ hard_iface->net_dev->name);
+
+ batadv_update_min_mtu(hard_iface->mesh_iface);
+
+ if (bat_priv->algo_ops->iface.activate)
+ bat_priv->algo_ops->iface.activate(hard_iface);
+
+out:
+ batadv_hardif_put(primary_if);
+}
+
+/**
+ * batadv_hardif_deactivate_interface() - move a hard interface to the
+ * inactive state
+ * @hard_iface: the interface that went down
+ *
+ * Demote @hard_iface to BATADV_IF_INACTIVE and recalculate the mesh minimum
+ * MTU based on the remaining active interfaces.
+ */
+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->mesh_iface, "Interface deactivated: %s\n",
+ hard_iface->net_dev->name);
+
+ batadv_update_min_mtu(hard_iface->mesh_iface);
+}
+
+/**
+ * batadv_hardif_enable_interface() - Enslave interface to mesh interface
+ * @net_dev: netdev struct of the interface to add to mesh interface
+ * @mesh_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 net_device *net_dev,
+ struct net_device *mesh_iface)
+{
+ int max_header_len = batadv_max_header_len();
+ __be16 ethertype = htons(ETH_P_BATMAN);
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_priv *bat_priv;
+ unsigned int required_mtu;
+ unsigned int hardif_mtu;
+ bool fragmentation;
+ int ret;
+
+ ASSERT_RTNL();
+
+ if (!batadv_is_valid_iface(net_dev))
+ return -EINVAL;
+
+ hardif_mtu = READ_ONCE(net_dev->mtu);
+ required_mtu = READ_ONCE(mesh_iface->mtu) + max_header_len;
+
+ if (hardif_mtu < ETH_MIN_MTU + max_header_len)
+ return -EINVAL;
+
+ hard_iface = kzalloc_obj(*hard_iface, GFP_ATOMIC);
+ if (!hard_iface)
+ return -ENOMEM;
+
+ netdev_hold(net_dev, &hard_iface->dev_tracker, GFP_ATOMIC);
+ hard_iface->net_dev = net_dev;
+
+ hard_iface->if_status = BATADV_IF_INACTIVE;
+
+ 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;
+ if (batadv_is_wifi_hardif(hard_iface))
+ hard_iface->num_bcasts = BATADV_NUM_BCASTS_WIRELESS;
+
+ WRITE_ONCE(hard_iface->hop_penalty, 0);
+
+ batadv_v_hardif_init(hard_iface);
+
+ netdev_hold(mesh_iface, &hard_iface->meshif_dev_tracker, GFP_ATOMIC);
+ hard_iface->mesh_iface = mesh_iface;
+ bat_priv = netdev_priv(hard_iface->mesh_iface);
+
+ bat_priv->hardif_generation++;
+ ret = netdev_master_upper_dev_link(hard_iface->net_dev,
+ mesh_iface, hard_iface, NULL, NULL);
+ if (ret)
+ goto err_dev;
+
+ ret = bat_priv->algo_ops->iface.enable(hard_iface);
+ if (ret < 0)
+ goto err_upper;
+
+ 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->mesh_iface, "Adding interface: %s\n",
+ hard_iface->net_dev->name);
+
+ fragmentation = READ_ONCE(bat_priv->fragmentation);
+ if (fragmentation && hardif_mtu < required_mtu)
+ batadv_info(hard_iface->mesh_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, hardif_mtu,
+ required_mtu);
+
+ if (!fragmentation && hardif_mtu < required_mtu)
+ batadv_info(hard_iface->mesh_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, hardif_mtu,
+ required_mtu);
+
+ batadv_check_known_mac_addr(hard_iface);
+
+ if (batadv_hardif_is_iface_up(hard_iface))
+ batadv_hardif_activate_interface(hard_iface);
+ else
+ batadv_err(hard_iface->mesh_iface,
+ "Not using interface %s (retrying later): interface not active\n",
+ hard_iface->net_dev->name);
+
+ batadv_hardif_recalc_extra_skbroom(mesh_iface);
+
+ if (bat_priv->algo_ops->iface.enabled)
+ bat_priv->algo_ops->iface.enabled(hard_iface);
+
+ return 0;
+
+err_upper:
+ netdev_upper_dev_unlink(hard_iface->net_dev, mesh_iface);
+err_dev:
+ batadv_hardif_put(hard_iface);
+ return ret;
+}
+
+/**
+ * batadv_hardif_disable_interface() - Remove hard interface from mesh 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->mesh_iface);
+ struct batadv_hard_iface *primary_if;
+
+ ASSERT_RTNL();
+
+ batadv_hardif_deactivate_interface(hard_iface);
+
+ if (hard_iface->if_status != BATADV_IF_INACTIVE)
+ return;
+
+ batadv_info(hard_iface->mesh_iface, "Removing interface: %s\n",
+ hard_iface->net_dev->name);
+ dev_remove_pack(&hard_iface->batman_adv_ptype);
+
+ 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->mesh_iface);
+ batadv_primary_if_select(bat_priv, new_if);
+
+ batadv_hardif_put(new_if);
+ }
+ batadv_hardif_put(primary_if);
+
+ bat_priv->algo_ops->iface.disable(hard_iface);
+ hard_iface->if_status = BATADV_IF_TO_BE_REMOVED;
+
+ /* delete all references to this hard_iface */
+ batadv_purge_orig_ref(bat_priv);
+ batadv_purge_outstanding_packets(bat_priv, hard_iface);
+
+ bat_priv->hardif_generation++;
+ netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface);
+ batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface);
+
+ /* nobody uses this mesh interface anymore */
+ if (list_empty(&hard_iface->mesh_iface->adj_list.lower))
+ batadv_gw_check_client_stop(bat_priv);
+
+ batadv_hardif_put(hard_iface);
+}
+
+/**
+ * batadv_hard_if_event_meshif() - Handle events for mesh interfaces
+ * @event: NETDEV_* event to handle
+ * @net_dev: net_device which generated an event
+ *
+ * Return: NOTIFY_* result
+ */
+static int batadv_hard_if_event_meshif(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_meshif_create_vlan(bat_priv, BATADV_NO_FLAGS);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * batadv_wifi_net_device_insert() - save information about wifi net_device
+ * @net_dev: net_device to add to batadv_wifi_net_devices
+ * @wifi_flags: extracted batadv_hard_iface_wifi_flags of a net_device
+ *
+ * Return: 0 on success, negative value on error
+ */
+static int
+batadv_wifi_net_device_insert(struct net_device *net_dev, u32 wifi_flags)
+{
+ struct batadv_wifi_net_device_state *device_state;
+ int ret;
+
+ ASSERT_RTNL();
+
+ device_state = kzalloc_obj(*device_state, GFP_KERNEL);
+ if (!device_state)
+ return -ENOMEM;
+
+ netdev_hold(net_dev, &device_state->dev_tracker, GFP_KERNEL);
+ device_state->netdev = net_dev;
+ WRITE_ONCE(device_state->wifi_flags, wifi_flags);
+
+ ret = rhashtable_insert_fast(&batadv_wifi_net_devices, &device_state->l,
+ batadv_wifi_net_devices_params);
+ if (ret < 0)
+ goto err_free;
+
+ return 0;
+
+err_free:
+ netdev_put(device_state->netdev, &device_state->dev_tracker);
+ kfree(device_state);
+ return ret;
+}
+
+/**
+ * batadv_wifi_net_device_remove() - remove information about wifi net_device
+ * @device_state: wifi net_device state to remove from batadv_wifi_net_devices
+ */
+static void
+batadv_wifi_net_device_remove(struct batadv_wifi_net_device_state *device_state)
+{
+ ASSERT_RTNL();
+
+ rhashtable_remove_fast(&batadv_wifi_net_devices, &device_state->l,
+ batadv_wifi_net_devices_params);
+ netdev_put(device_state->netdev, &device_state->dev_tracker);
+ kfree_rcu(device_state, rcu);
+}
+
+/**
+ * batadv_wifi_net_device_update() - update wifi state of net_device
+ * @net_dev: net_device to update in batadv_wifi_net_devices
+ *
+ * The device will only be stored in batadv_wifi_net_devices when
+ * it could be identified as wifi device. If the net_device is no
+ * longer a wifi device, it is automatically removed from
+ * batadv_wifi_net_devices.
+ */
+static void
+batadv_wifi_net_device_update(struct net_device *net_dev)
+{
+ struct batadv_wifi_net_device_state *device_state;
+ u32 wifi_flags;
+
+ ASSERT_RTNL();
+
+ wifi_flags = batadv_wifi_flags_evaluate(net_dev);
+ device_state = rhashtable_lookup_fast(&batadv_wifi_net_devices,
+ &net_dev,
+ batadv_wifi_net_devices_params);
+
+ if (device_state) {
+ if (batadv_is_wifi(wifi_flags))
+ WRITE_ONCE(device_state->wifi_flags, wifi_flags);
+ else
+ batadv_wifi_net_device_remove(device_state);
+ } else if (batadv_is_wifi(wifi_flags)) {
+ batadv_wifi_net_device_insert(net_dev, wifi_flags);
+ }
+}
+
+/**
+ * batadv_wifi_net_device_unregister() - remove wifi state of net_device
+ * @net_dev: net_device to remove from batadv_wifi_net_devices
+ */
+static void
+batadv_wifi_net_device_unregister(struct net_device *net_dev)
+{
+ struct batadv_wifi_net_device_state *device_state;
+
+ ASSERT_RTNL();
+
+ device_state = rhashtable_lookup_fast(&batadv_wifi_net_devices,
+ &net_dev,
+ batadv_wifi_net_devices_params);
+ if (!device_state)
+ return;
+
+ batadv_wifi_net_device_remove(device_state);
+}
+
+/**
+ * batadv_wifi_net_device_event() - handle network events for batadv_wifi_net_devices
+ * @event: enum netdev_cmd event to handle
+ * @net_dev: net_device to update in batadv_wifi_net_devices
+ */
+static void batadv_wifi_net_device_event(unsigned long event,
+ struct net_device *net_dev)
+{
+ switch (event) {
+ case NETDEV_REGISTER:
+ case NETDEV_POST_TYPE_CHANGE:
+ case NETDEV_CHANGEUPPER:
+ batadv_wifi_net_device_update(net_dev);
+ break;
+ case NETDEV_UNREGISTER:
+ case NETDEV_PRE_TYPE_CHANGE:
+ batadv_wifi_net_device_unregister(net_dev);
+ break;
+ }
+}
+
+/**
+ * batadv_hard_if_event() - netdevice notifier callback for hard interfaces
+ * @this: notifier block (unused)
+ * @event: the NETDEV_* event to handle
+ * @ptr: notifier info pointing to the affected net_device
+ *
+ * Dispatch netdevice events that affect potential or existing hard
+ * interfaces. Mesh interfaces are handled separately by
+ * batadv_hard_if_event_meshif().
+ *
+ * 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 *primary_if = NULL;
+ struct batadv_hard_iface *hard_iface;
+ struct batadv_priv *bat_priv;
+
+ if (batadv_meshif_is_valid(net_dev))
+ return batadv_hard_if_event_meshif(event, net_dev);
+
+ batadv_wifi_net_device_event(event, net_dev);
+
+ hard_iface = batadv_hardif_get_by_netdev(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:
+ batadv_hardif_disable_interface(hard_iface);
+ break;
+ case NETDEV_CHANGEMTU:
+ batadv_update_min_mtu(hard_iface->mesh_iface);
+ break;
+ case NETDEV_CHANGEADDR:
+ batadv_check_known_mac_addr(hard_iface);
+
+ bat_priv = netdev_priv(hard_iface->mesh_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_REGISTER:
+ case NETDEV_POST_TYPE_CHANGE:
+ case NETDEV_CHANGEUPPER:
+ 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,
+};
+
+/**
+ * batadv_wifi_net_devices_init() - Initialize wifi devices cache
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int __init batadv_wifi_net_devices_init(void)
+{
+ return rhashtable_init(&batadv_wifi_net_devices,
+ &batadv_wifi_net_devices_params);
+}
+
+/**
+ * batadv_wifi_net_devices_deinit() - Deinitialize wifi devices cache
+ */
+void batadv_wifi_net_devices_deinit(void)
+{
+ /* just destroy table. entries should have been removed by
+ * unregister_netdevice_notifier() and the corresponding
+ * NETDEV_UNREGISTER events
+ */
+ rhashtable_destroy(&batadv_wifi_net_devices);
+}