diff options
Diffstat (limited to 'net/batman-adv')
46 files changed, 20118 insertions, 0 deletions
diff --git a/net/batman-adv/Kconfig b/net/batman-adv/Kconfig new file mode 100644 index 00000000..fa780b76 --- /dev/null +++ b/net/batman-adv/Kconfig @@ -0,0 +1,60 @@ +# +# B.A.T.M.A.N meshing protocol +# + +config BATMAN_ADV + tristate "B.A.T.M.A.N. Advanced Meshing Protocol" + depends on NET + select CRC16 + select LIBCRC32C + default n + help + B.A.T.M.A.N. (better approach to mobile ad-hoc networking) is + a routing protocol for multi-hop ad-hoc mesh networks. The + networks may be wired or wireless. See + http://www.open-mesh.org/ for more information and user space + tools. + +config BATMAN_ADV_BLA + bool "Bridge Loop Avoidance" + depends on BATMAN_ADV && INET + default y + help + This option enables BLA (Bridge Loop Avoidance), a mechanism + to avoid Ethernet frames looping when mesh nodes are connected + to both the same LAN and the same mesh. If you will never use + more than one mesh node in the same LAN, you can safely remove + this feature and save some space. + +config BATMAN_ADV_DAT + bool "Distributed ARP Table" + depends on BATMAN_ADV && INET + default n + help + This option enables DAT (Distributed ARP Table), a DHT based + mechanism that increases ARP reliability on sparse wireless + mesh networks. If you think that your network does not need + this option you can safely remove it and save some space. + +config BATMAN_ADV_NC + bool "Network Coding" + depends on BATMAN_ADV + default n + help + This option enables network coding, a mechanism that aims to + increase the overall network throughput by fusing multiple + packets in one transmission. + Note that interfaces controlled by batman-adv must be manually + configured to have promiscuous mode enabled in order to make + network coding work. + If you think that your network does not need this feature you + can safely disable it and save some space. + +config BATMAN_ADV_DEBUG + bool "B.A.T.M.A.N. debugging" + depends on BATMAN_ADV + help + This is an option for use by developers; most people should + say N here. This enables compilation of support for + outputting debugging information to the kernel log. The + output is controlled via the module parameter debug. diff --git a/net/batman-adv/Makefile b/net/batman-adv/Makefile new file mode 100644 index 00000000..acbac2a9 --- /dev/null +++ b/net/batman-adv/Makefile @@ -0,0 +1,42 @@ +# +# Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: +# +# Marek Lindner, Simon Wunderlich +# +# 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 +# + +obj-$(CONFIG_BATMAN_ADV) += batman-adv.o +batman-adv-y += bat_iv_ogm.o +batman-adv-y += bitarray.o +batman-adv-$(CONFIG_BATMAN_ADV_BLA) += bridge_loop_avoidance.o +batman-adv-y += debugfs.o +batman-adv-$(CONFIG_BATMAN_ADV_DAT) += distributed-arp-table.o +batman-adv-y += gateway_client.o +batman-adv-y += gateway_common.o +batman-adv-y += hard-interface.o +batman-adv-y += hash.o +batman-adv-y += icmp_socket.o +batman-adv-y += main.o +batman-adv-$(CONFIG_BATMAN_ADV_NC) += network-coding.o +batman-adv-y += originator.o +batman-adv-y += ring_buffer.o +batman-adv-y += routing.o +batman-adv-y += send.o +batman-adv-y += soft-interface.o +batman-adv-y += sysfs.o +batman-adv-y += translation-table.o +batman-adv-y += unicast.o +batman-adv-y += vis.o diff --git a/net/batman-adv/bat_algo.h b/net/batman-adv/bat_algo.h new file mode 100644 index 00000000..a4808c29 --- /dev/null +++ b/net/batman-adv/bat_algo.h @@ -0,0 +1,25 @@ +/* Copyright (C) 2011-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_BAT_ALGO_H_ +#define _NET_BATMAN_ADV_BAT_ALGO_H_ + +int batadv_iv_init(void); + +#endif /* _NET_BATMAN_ADV_BAT_ALGO_H_ */ diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c new file mode 100644 index 00000000..f680ee10 --- /dev/null +++ b/net/batman-adv/bat_iv_ogm.c @@ -0,0 +1,1368 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 "ring_buffer.h" +#include "originator.h" +#include "routing.h" +#include "gateway_common.h" +#include "gateway_client.h" +#include "hard-interface.h" +#include "send.h" +#include "bat_algo.h" +#include "network-coding.h" + +/** + * batadv_dup_status - duplicate status + * @BATADV_NO_DUP: the packet is a duplicate + * @BATADV_ORIG_DUP: OGM is a duplicate in the originator (but not for the + * neighbor) + * @BATADV_NEIGH_DUP: OGM is a duplicate for the neighbor + * @BATADV_PROTECTED: originator is currently protected (after reboot) + */ +enum batadv_dup_status { + BATADV_NO_DUP = 0, + BATADV_ORIG_DUP, + BATADV_NEIGH_DUP, + BATADV_PROTECTED, +}; + +static struct batadv_neigh_node * +batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, + const uint8_t *neigh_addr, + struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh, __be32 seqno) +{ + struct batadv_neigh_node *neigh_node; + + neigh_node = batadv_neigh_node_new(hard_iface, neigh_addr, + ntohl(seqno)); + if (!neigh_node) + goto out; + + INIT_LIST_HEAD(&neigh_node->bonding_list); + + neigh_node->orig_node = orig_neigh; + neigh_node->if_incoming = hard_iface; + + spin_lock_bh(&orig_node->neigh_list_lock); + hlist_add_head_rcu(&neigh_node->list, &orig_node->neigh_list); + spin_unlock_bh(&orig_node->neigh_list_lock); + +out: + return neigh_node; +} + +static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) +{ + struct batadv_ogm_packet *batadv_ogm_packet; + unsigned char *ogm_buff; + uint32_t random_seqno; + int res = -ENOMEM; + + /* randomize initial seqno to avoid collision */ + get_random_bytes(&random_seqno, sizeof(random_seqno)); + atomic_set(&hard_iface->bat_iv.ogm_seqno, random_seqno); + + hard_iface->bat_iv.ogm_buff_len = BATADV_OGM_HLEN; + ogm_buff = kmalloc(hard_iface->bat_iv.ogm_buff_len, GFP_ATOMIC); + if (!ogm_buff) + goto out; + + hard_iface->bat_iv.ogm_buff = ogm_buff; + + batadv_ogm_packet = (struct batadv_ogm_packet *)ogm_buff; + batadv_ogm_packet->header.packet_type = BATADV_IV_OGM; + batadv_ogm_packet->header.version = BATADV_COMPAT_VERSION; + batadv_ogm_packet->header.ttl = 2; + batadv_ogm_packet->flags = BATADV_NO_FLAGS; + batadv_ogm_packet->tq = BATADV_TQ_MAX_VALUE; + batadv_ogm_packet->tt_num_changes = 0; + batadv_ogm_packet->ttvn = 0; + + res = 0; + +out: + return res; +} + +static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) +{ + kfree(hard_iface->bat_iv.ogm_buff); + hard_iface->bat_iv.ogm_buff = NULL; +} + +static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface) +{ + struct batadv_ogm_packet *batadv_ogm_packet; + unsigned char *ogm_buff = hard_iface->bat_iv.ogm_buff; + + batadv_ogm_packet = (struct batadv_ogm_packet *)ogm_buff; + memcpy(batadv_ogm_packet->orig, + hard_iface->net_dev->dev_addr, ETH_ALEN); + memcpy(batadv_ogm_packet->prev_sender, + hard_iface->net_dev->dev_addr, ETH_ALEN); +} + +static void +batadv_iv_ogm_primary_iface_set(struct batadv_hard_iface *hard_iface) +{ + struct batadv_ogm_packet *batadv_ogm_packet; + unsigned char *ogm_buff = hard_iface->bat_iv.ogm_buff; + + batadv_ogm_packet = (struct batadv_ogm_packet *)ogm_buff; + batadv_ogm_packet->flags = BATADV_PRIMARIES_FIRST_HOP; + batadv_ogm_packet->header.ttl = BATADV_TTL; +} + +/* when do we schedule our own ogm to be sent */ +static unsigned long +batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) +{ + unsigned int msecs; + + msecs = atomic_read(&bat_priv->orig_interval) - BATADV_JITTER; + msecs += prandom_u32() % (2 * BATADV_JITTER); + + return jiffies + msecs_to_jiffies(msecs); +} + +/* when do we schedule a ogm packet to be sent */ +static unsigned long batadv_iv_ogm_fwd_send_time(void) +{ + return jiffies + msecs_to_jiffies(prandom_u32() % (BATADV_JITTER / 2)); +} + +/* apply hop penalty for a normal link */ +static uint8_t batadv_hop_penalty(uint8_t tq, + const struct batadv_priv *bat_priv) +{ + int hop_penalty = atomic_read(&bat_priv->hop_penalty); + int new_tq; + + new_tq = tq * (BATADV_TQ_MAX_VALUE - hop_penalty); + new_tq /= BATADV_TQ_MAX_VALUE; + + return new_tq; +} + +/* is there another aggregated packet here? */ +static int batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len, + int tt_num_changes) +{ + int next_buff_pos = 0; + + next_buff_pos += buff_pos + BATADV_OGM_HLEN; + next_buff_pos += batadv_tt_len(tt_num_changes); + + return (next_buff_pos <= packet_len) && + (next_buff_pos <= BATADV_MAX_AGGREGATION_BYTES); +} + +/* send a batman ogm to a given interface */ +static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, + struct batadv_hard_iface *hard_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + char *fwd_str; + uint8_t packet_num; + int16_t buff_pos; + struct batadv_ogm_packet *batadv_ogm_packet; + struct sk_buff *skb; + uint8_t *packet_pos; + + if (hard_iface->if_status != BATADV_IF_ACTIVE) + return; + + packet_num = 0; + buff_pos = 0; + packet_pos = forw_packet->skb->data; + batadv_ogm_packet = (struct batadv_ogm_packet *)packet_pos; + + /* adjust all flags and log packets */ + while (batadv_iv_ogm_aggr_packet(buff_pos, forw_packet->packet_len, + batadv_ogm_packet->tt_num_changes)) { + /* we might have aggregated direct link packets with an + * ordinary base packet + */ + if (forw_packet->direct_link_flags & BIT(packet_num) && + forw_packet->if_incoming == hard_iface) + batadv_ogm_packet->flags |= BATADV_DIRECTLINK; + else + batadv_ogm_packet->flags &= ~BATADV_DIRECTLINK; + + if (packet_num > 0 || !forw_packet->own) + fwd_str = "Forwarding"; + else + fwd_str = "Sending own"; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "%s %spacket (originator %pM, seqno %u, TQ %d, TTL %d, IDF %s, ttvn %d) on interface %s [%pM]\n", + fwd_str, (packet_num > 0 ? "aggregated " : ""), + batadv_ogm_packet->orig, + ntohl(batadv_ogm_packet->seqno), + batadv_ogm_packet->tq, batadv_ogm_packet->header.ttl, + (batadv_ogm_packet->flags & BATADV_DIRECTLINK ? + "on" : "off"), + batadv_ogm_packet->ttvn, hard_iface->net_dev->name, + hard_iface->net_dev->dev_addr); + + buff_pos += BATADV_OGM_HLEN; + buff_pos += batadv_tt_len(batadv_ogm_packet->tt_num_changes); + packet_num++; + packet_pos = forw_packet->skb->data + buff_pos; + batadv_ogm_packet = (struct batadv_ogm_packet *)packet_pos; + } + + /* create clone because function is called more than once */ + skb = skb_clone(forw_packet->skb, GFP_ATOMIC); + if (skb) { + batadv_inc_counter(bat_priv, BATADV_CNT_MGMT_TX); + batadv_add_counter(bat_priv, BATADV_CNT_MGMT_TX_BYTES, + skb->len + ETH_HLEN); + batadv_send_skb_packet(skb, hard_iface, batadv_broadcast_addr); + } +} + +/* send a batman ogm packet */ +static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) +{ + struct batadv_hard_iface *hard_iface; + struct net_device *soft_iface; + struct batadv_priv *bat_priv; + struct batadv_hard_iface *primary_if = NULL; + struct batadv_ogm_packet *batadv_ogm_packet; + unsigned char directlink; + uint8_t *packet_pos; + + packet_pos = forw_packet->skb->data; + batadv_ogm_packet = (struct batadv_ogm_packet *)packet_pos; + directlink = (batadv_ogm_packet->flags & BATADV_DIRECTLINK ? 1 : 0); + + if (!forw_packet->if_incoming) { + pr_err("Error - can't forward packet: incoming iface not specified\n"); + goto out; + } + + soft_iface = forw_packet->if_incoming->soft_iface; + bat_priv = netdev_priv(soft_iface); + + if (forw_packet->if_incoming->if_status != BATADV_IF_ACTIVE) + goto out; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + /* multihomed peer assumed + * non-primary OGMs are only broadcasted on their interface + */ + if ((directlink && (batadv_ogm_packet->header.ttl == 1)) || + (forw_packet->own && (forw_packet->if_incoming != primary_if))) { + /* FIXME: what about aggregated packets ? */ + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "%s packet (originator %pM, seqno %u, TTL %d) on interface %s [%pM]\n", + (forw_packet->own ? "Sending own" : "Forwarding"), + batadv_ogm_packet->orig, + ntohl(batadv_ogm_packet->seqno), + batadv_ogm_packet->header.ttl, + forw_packet->if_incoming->net_dev->name, + forw_packet->if_incoming->net_dev->dev_addr); + + /* skb is only used once and than forw_packet is free'd */ + batadv_send_skb_packet(forw_packet->skb, + forw_packet->if_incoming, + batadv_broadcast_addr); + forw_packet->skb = NULL; + + goto out; + } + + /* broadcast on every interface */ + rcu_read_lock(); + list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { + if (hard_iface->soft_iface != soft_iface) + continue; + + batadv_iv_ogm_send_to_if(forw_packet, hard_iface); + } + rcu_read_unlock(); + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); +} + +/* return true if new_packet can be aggregated with forw_packet */ +static bool +batadv_iv_ogm_can_aggregate(const struct batadv_ogm_packet *new_bat_ogm_packet, + struct batadv_priv *bat_priv, + int packet_len, unsigned long send_time, + bool directlink, + const struct batadv_hard_iface *if_incoming, + const struct batadv_forw_packet *forw_packet) +{ + struct batadv_ogm_packet *batadv_ogm_packet; + int aggregated_bytes = forw_packet->packet_len + packet_len; + struct batadv_hard_iface *primary_if = NULL; + bool res = false; + unsigned long aggregation_end_time; + + batadv_ogm_packet = (struct batadv_ogm_packet *)forw_packet->skb->data; + aggregation_end_time = send_time; + aggregation_end_time += msecs_to_jiffies(BATADV_MAX_AGGREGATION_MS); + + /* we can aggregate the current packet to this aggregated packet + * if: + * + * - the send time is within our MAX_AGGREGATION_MS time + * - the resulting packet wont be bigger than + * MAX_AGGREGATION_BYTES + */ + if (time_before(send_time, forw_packet->send_time) && + time_after_eq(aggregation_end_time, forw_packet->send_time) && + (aggregated_bytes <= BATADV_MAX_AGGREGATION_BYTES)) { + /* check aggregation compatibility + * -> direct link packets are broadcasted on + * their interface only + * -> aggregate packet if the current packet is + * a "global" packet as well as the base + * packet + */ + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + /* packets without direct link flag and high TTL + * are flooded through the net + */ + if ((!directlink) && + (!(batadv_ogm_packet->flags & BATADV_DIRECTLINK)) && + (batadv_ogm_packet->header.ttl != 1) && + + /* own packets originating non-primary + * interfaces leave only that interface + */ + ((!forw_packet->own) || + (forw_packet->if_incoming == primary_if))) { + res = true; + goto out; + } + + /* if the incoming packet is sent via this one + * interface only - we still can aggregate + */ + if ((directlink) && + (new_bat_ogm_packet->header.ttl == 1) && + (forw_packet->if_incoming == if_incoming) && + + /* packets from direct neighbors or + * own secondary interface packets + * (= secondary interface packets in general) + */ + (batadv_ogm_packet->flags & BATADV_DIRECTLINK || + (forw_packet->own && + forw_packet->if_incoming != primary_if))) { + res = true; + goto out; + } + } + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return res; +} + +/* create a new aggregated packet and add this packet to it */ +static void batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, + int packet_len, unsigned long send_time, + bool direct_link, + struct batadv_hard_iface *if_incoming, + int own_packet) +{ + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_forw_packet *forw_packet_aggr; + unsigned char *skb_buff; + unsigned int skb_size; + + if (!atomic_inc_not_zero(&if_incoming->refcount)) + return; + + /* own packet should always be scheduled */ + if (!own_packet) { + if (!batadv_atomic_dec_not_zero(&bat_priv->batman_queue_left)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "batman packet queue full\n"); + goto out; + } + } + + forw_packet_aggr = kmalloc(sizeof(*forw_packet_aggr), GFP_ATOMIC); + if (!forw_packet_aggr) { + if (!own_packet) + atomic_inc(&bat_priv->batman_queue_left); + goto out; + } + + if ((atomic_read(&bat_priv->aggregated_ogms)) && + (packet_len < BATADV_MAX_AGGREGATION_BYTES)) + skb_size = BATADV_MAX_AGGREGATION_BYTES; + else + skb_size = packet_len; + + skb_size += ETH_HLEN + NET_IP_ALIGN; + + forw_packet_aggr->skb = dev_alloc_skb(skb_size); + if (!forw_packet_aggr->skb) { + if (!own_packet) + atomic_inc(&bat_priv->batman_queue_left); + kfree(forw_packet_aggr); + goto out; + } + skb_reserve(forw_packet_aggr->skb, ETH_HLEN + NET_IP_ALIGN); + + INIT_HLIST_NODE(&forw_packet_aggr->list); + + skb_buff = skb_put(forw_packet_aggr->skb, packet_len); + forw_packet_aggr->packet_len = packet_len; + memcpy(skb_buff, packet_buff, packet_len); + + forw_packet_aggr->own = own_packet; + forw_packet_aggr->if_incoming = if_incoming; + forw_packet_aggr->num_packets = 0; + forw_packet_aggr->direct_link_flags = BATADV_NO_FLAGS; + forw_packet_aggr->send_time = send_time; + + /* save packet direct link flag status */ + if (direct_link) + forw_packet_aggr->direct_link_flags |= 1; + + /* add new packet to packet list */ + spin_lock_bh(&bat_priv->forw_bat_list_lock); + hlist_add_head(&forw_packet_aggr->list, &bat_priv->forw_bat_list); + spin_unlock_bh(&bat_priv->forw_bat_list_lock); + + /* start timer for this packet */ + INIT_DELAYED_WORK(&forw_packet_aggr->delayed_work, + batadv_send_outstanding_bat_ogm_packet); + queue_delayed_work(batadv_event_workqueue, + &forw_packet_aggr->delayed_work, + send_time - jiffies); + + return; +out: + batadv_hardif_free_ref(if_incoming); +} + +/* aggregate a new packet into the existing ogm packet */ +static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr, + const unsigned char *packet_buff, + int packet_len, bool direct_link) +{ + unsigned char *skb_buff; + unsigned long new_direct_link_flag; + + skb_buff = skb_put(forw_packet_aggr->skb, packet_len); + memcpy(skb_buff, packet_buff, packet_len); + forw_packet_aggr->packet_len += packet_len; + forw_packet_aggr->num_packets++; + + /* save packet direct link flag status */ + if (direct_link) { + new_direct_link_flag = BIT(forw_packet_aggr->num_packets); + forw_packet_aggr->direct_link_flags |= new_direct_link_flag; + } +} + +static void batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv, + unsigned char *packet_buff, + int packet_len, + struct batadv_hard_iface *if_incoming, + int own_packet, unsigned long send_time) +{ + /* _aggr -> pointer to the packet we want to aggregate with + * _pos -> pointer to the position in the queue + */ + struct batadv_forw_packet *forw_packet_aggr = NULL; + struct batadv_forw_packet *forw_packet_pos = NULL; + struct batadv_ogm_packet *batadv_ogm_packet; + bool direct_link; + unsigned long max_aggregation_jiffies; + + batadv_ogm_packet = (struct batadv_ogm_packet *)packet_buff; + direct_link = batadv_ogm_packet->flags & BATADV_DIRECTLINK ? 1 : 0; + max_aggregation_jiffies = msecs_to_jiffies(BATADV_MAX_AGGREGATION_MS); + + /* find position for the packet in the forward queue */ + spin_lock_bh(&bat_priv->forw_bat_list_lock); + /* own packets are not to be aggregated */ + if ((atomic_read(&bat_priv->aggregated_ogms)) && (!own_packet)) { + hlist_for_each_entry(forw_packet_pos, + &bat_priv->forw_bat_list, list) { + if (batadv_iv_ogm_can_aggregate(batadv_ogm_packet, + bat_priv, packet_len, + send_time, direct_link, + if_incoming, + forw_packet_pos)) { + forw_packet_aggr = forw_packet_pos; + break; + } + } + } + + /* nothing to aggregate with - either aggregation disabled or no + * suitable aggregation packet found + */ + if (!forw_packet_aggr) { + /* the following section can run without the lock */ + spin_unlock_bh(&bat_priv->forw_bat_list_lock); + + /* if we could not aggregate this packet with one of the others + * we hold it back for a while, so that it might be aggregated + * later on + */ + if (!own_packet && atomic_read(&bat_priv->aggregated_ogms)) + send_time += max_aggregation_jiffies; + + batadv_iv_ogm_aggregate_new(packet_buff, packet_len, + send_time, direct_link, + if_incoming, own_packet); + } else { + batadv_iv_ogm_aggregate(forw_packet_aggr, packet_buff, + packet_len, direct_link); + spin_unlock_bh(&bat_priv->forw_bat_list_lock); + } +} + +static void batadv_iv_ogm_forward(struct batadv_orig_node *orig_node, + const struct ethhdr *ethhdr, + struct batadv_ogm_packet *batadv_ogm_packet, + bool is_single_hop_neigh, + bool is_from_best_next_hop, + struct batadv_hard_iface *if_incoming) +{ + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + uint8_t tt_num_changes; + + if (batadv_ogm_packet->header.ttl <= 1) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, "ttl exceeded\n"); + return; + } + + if (!is_from_best_next_hop) { + /* Mark the forwarded packet when it is not coming from our + * best next hop. We still need to forward the packet for our + * neighbor link quality detection to work in case the packet + * originated from a single hop neighbor. Otherwise we can + * simply drop the ogm. + */ + if (is_single_hop_neigh) + batadv_ogm_packet->flags |= BATADV_NOT_BEST_NEXT_HOP; + else + return; + } + + tt_num_changes = batadv_ogm_packet->tt_num_changes; + + batadv_ogm_packet->header.ttl--; + memcpy(batadv_ogm_packet->prev_sender, ethhdr->h_source, ETH_ALEN); + + /* apply hop penalty */ + batadv_ogm_packet->tq = batadv_hop_penalty(batadv_ogm_packet->tq, + bat_priv); + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Forwarding packet: tq: %i, ttl: %i\n", + batadv_ogm_packet->tq, batadv_ogm_packet->header.ttl); + + /* switch of primaries first hop flag when forwarding */ + batadv_ogm_packet->flags &= ~BATADV_PRIMARIES_FIRST_HOP; + if (is_single_hop_neigh) + batadv_ogm_packet->flags |= BATADV_DIRECTLINK; + else + batadv_ogm_packet->flags &= ~BATADV_DIRECTLINK; + + batadv_iv_ogm_queue_add(bat_priv, (unsigned char *)batadv_ogm_packet, + BATADV_OGM_HLEN + batadv_tt_len(tt_num_changes), + if_incoming, 0, batadv_iv_ogm_fwd_send_time()); +} + +static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + unsigned char **ogm_buff = &hard_iface->bat_iv.ogm_buff; + struct batadv_ogm_packet *batadv_ogm_packet; + struct batadv_hard_iface *primary_if; + int *ogm_buff_len = &hard_iface->bat_iv.ogm_buff_len; + int vis_server, tt_num_changes = 0; + uint32_t seqno; + uint8_t bandwidth; + + vis_server = atomic_read(&bat_priv->vis_mode); + primary_if = batadv_primary_if_get_selected(bat_priv); + + if (hard_iface == primary_if) + tt_num_changes = batadv_tt_append_diff(bat_priv, ogm_buff, + ogm_buff_len, + BATADV_OGM_HLEN); + + batadv_ogm_packet = (struct batadv_ogm_packet *)(*ogm_buff); + + /* change sequence number to network order */ + seqno = (uint32_t)atomic_read(&hard_iface->bat_iv.ogm_seqno); + batadv_ogm_packet->seqno = htonl(seqno); + atomic_inc(&hard_iface->bat_iv.ogm_seqno); + + batadv_ogm_packet->ttvn = atomic_read(&bat_priv->tt.vn); + batadv_ogm_packet->tt_crc = htons(bat_priv->tt.local_crc); + if (tt_num_changes >= 0) + batadv_ogm_packet->tt_num_changes = tt_num_changes; + + if (vis_server == BATADV_VIS_TYPE_SERVER_SYNC) + batadv_ogm_packet->flags |= BATADV_VIS_SERVER; + else + batadv_ogm_packet->flags &= ~BATADV_VIS_SERVER; + + if (hard_iface == primary_if && + atomic_read(&bat_priv->gw_mode) == BATADV_GW_MODE_SERVER) { + bandwidth = (uint8_t)atomic_read(&bat_priv->gw_bandwidth); + batadv_ogm_packet->gw_flags = bandwidth; + } else { + batadv_ogm_packet->gw_flags = BATADV_NO_FLAGS; + } + + batadv_slide_own_bcast_window(hard_iface); + batadv_iv_ogm_queue_add(bat_priv, hard_iface->bat_iv.ogm_buff, + hard_iface->bat_iv.ogm_buff_len, hard_iface, 1, + batadv_iv_ogm_emit_send_time(bat_priv)); + + if (primary_if) + batadv_hardif_free_ref(primary_if); +} + +static void +batadv_iv_ogm_orig_update(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const struct ethhdr *ethhdr, + const struct batadv_ogm_packet *batadv_ogm_packet, + struct batadv_hard_iface *if_incoming, + const unsigned char *tt_buff, + enum batadv_dup_status dup_status) +{ + struct batadv_neigh_node *neigh_node = NULL, *tmp_neigh_node = NULL; + struct batadv_neigh_node *router = NULL; + struct batadv_orig_node *orig_node_tmp; + int if_num; + uint8_t sum_orig, sum_neigh; + uint8_t *neigh_addr; + uint8_t tq_avg; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "update_originator(): Searching and updating originator entry of received packet\n"); + + rcu_read_lock(); + hlist_for_each_entry_rcu(tmp_neigh_node, + &orig_node->neigh_list, list) { + neigh_addr = tmp_neigh_node->addr; + if (batadv_compare_eth(neigh_addr, ethhdr->h_source) && + tmp_neigh_node->if_incoming == if_incoming && + atomic_inc_not_zero(&tmp_neigh_node->refcount)) { + if (neigh_node) + batadv_neigh_node_free_ref(neigh_node); + neigh_node = tmp_neigh_node; + continue; + } + + if (dup_status != BATADV_NO_DUP) + continue; + + spin_lock_bh(&tmp_neigh_node->lq_update_lock); + batadv_ring_buffer_set(tmp_neigh_node->tq_recv, + &tmp_neigh_node->tq_index, 0); + tq_avg = batadv_ring_buffer_avg(tmp_neigh_node->tq_recv); + tmp_neigh_node->tq_avg = tq_avg; + spin_unlock_bh(&tmp_neigh_node->lq_update_lock); + } + + if (!neigh_node) { + struct batadv_orig_node *orig_tmp; + + orig_tmp = batadv_get_orig_node(bat_priv, ethhdr->h_source); + if (!orig_tmp) + goto unlock; + + neigh_node = batadv_iv_ogm_neigh_new(if_incoming, + ethhdr->h_source, + orig_node, orig_tmp, + batadv_ogm_packet->seqno); + + batadv_orig_node_free_ref(orig_tmp); + if (!neigh_node) + goto unlock; + } else + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Updating existing last-hop neighbor of originator\n"); + + rcu_read_unlock(); + + orig_node->flags = batadv_ogm_packet->flags; + neigh_node->last_seen = jiffies; + + spin_lock_bh(&neigh_node->lq_update_lock); + batadv_ring_buffer_set(neigh_node->tq_recv, + &neigh_node->tq_index, + batadv_ogm_packet->tq); + neigh_node->tq_avg = batadv_ring_buffer_avg(neigh_node->tq_recv); + spin_unlock_bh(&neigh_node->lq_update_lock); + + if (dup_status == BATADV_NO_DUP) { + orig_node->last_ttl = batadv_ogm_packet->header.ttl; + neigh_node->last_ttl = batadv_ogm_packet->header.ttl; + } + + batadv_bonding_candidate_add(orig_node, neigh_node); + + /* if this neighbor already is our next hop there is nothing + * to change + */ + router = batadv_orig_node_get_router(orig_node); + if (router == neigh_node) + goto update_tt; + + /* if this neighbor does not offer a better TQ we won't consider it */ + if (router && (router->tq_avg > neigh_node->tq_avg)) + goto update_tt; + + /* if the TQ is the same and the link not more symmetric we + * won't consider it either + */ + if (router && (neigh_node->tq_avg == router->tq_avg)) { + orig_node_tmp = router->orig_node; + spin_lock_bh(&orig_node_tmp->ogm_cnt_lock); + if_num = router->if_incoming->if_num; + sum_orig = orig_node_tmp->bcast_own_sum[if_num]; + spin_unlock_bh(&orig_node_tmp->ogm_cnt_lock); + + orig_node_tmp = neigh_node->orig_node; + spin_lock_bh(&orig_node_tmp->ogm_cnt_lock); + if_num = neigh_node->if_incoming->if_num; + sum_neigh = orig_node_tmp->bcast_own_sum[if_num]; + spin_unlock_bh(&orig_node_tmp->ogm_cnt_lock); + + if (sum_orig >= sum_neigh) + goto update_tt; + } + + batadv_update_route(bat_priv, orig_node, neigh_node); + +update_tt: + /* I have to check for transtable changes only if the OGM has been + * sent through a primary interface + */ + if (((batadv_ogm_packet->orig != ethhdr->h_source) && + (batadv_ogm_packet->header.ttl > 2)) || + (batadv_ogm_packet->flags & BATADV_PRIMARIES_FIRST_HOP)) + batadv_tt_update_orig(bat_priv, orig_node, tt_buff, + batadv_ogm_packet->tt_num_changes, + batadv_ogm_packet->ttvn, + ntohs(batadv_ogm_packet->tt_crc)); + + if (orig_node->gw_flags != batadv_ogm_packet->gw_flags) + batadv_gw_node_update(bat_priv, orig_node, + batadv_ogm_packet->gw_flags); + + orig_node->gw_flags = batadv_ogm_packet->gw_flags; + + /* restart gateway selection if fast or late switching was enabled */ + if ((orig_node->gw_flags) && + (atomic_read(&bat_priv->gw_mode) == BATADV_GW_MODE_CLIENT) && + (atomic_read(&bat_priv->gw_sel_class) > 2)) + batadv_gw_check_election(bat_priv, orig_node); + + goto out; + +unlock: + rcu_read_unlock(); +out: + if (neigh_node) + batadv_neigh_node_free_ref(neigh_node); + if (router) + batadv_neigh_node_free_ref(router); +} + +static int batadv_iv_ogm_calc_tq(struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + struct batadv_ogm_packet *batadv_ogm_packet, + struct batadv_hard_iface *if_incoming) +{ + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_neigh_node *neigh_node = NULL, *tmp_neigh_node; + uint8_t total_count; + uint8_t orig_eq_count, neigh_rq_count, neigh_rq_inv, tq_own; + unsigned int neigh_rq_inv_cube, neigh_rq_max_cube; + int tq_asym_penalty, inv_asym_penalty, ret = 0; + unsigned int combined_tq; + + /* find corresponding one hop neighbor */ + rcu_read_lock(); + hlist_for_each_entry_rcu(tmp_neigh_node, + &orig_neigh_node->neigh_list, list) { + if (!batadv_compare_eth(tmp_neigh_node->addr, + orig_neigh_node->orig)) + continue; + + if (tmp_neigh_node->if_incoming != if_incoming) + continue; + + if (!atomic_inc_not_zero(&tmp_neigh_node->refcount)) + continue; + + neigh_node = tmp_neigh_node; + break; + } + rcu_read_unlock(); + + if (!neigh_node) + neigh_node = batadv_iv_ogm_neigh_new(if_incoming, + orig_neigh_node->orig, + orig_neigh_node, + orig_neigh_node, + batadv_ogm_packet->seqno); + + if (!neigh_node) + goto out; + + /* if orig_node is direct neighbor update neigh_node last_seen */ + if (orig_node == orig_neigh_node) + neigh_node->last_seen = jiffies; + + orig_node->last_seen = jiffies; + + /* find packet count of corresponding one hop neighbor */ + spin_lock_bh(&orig_node->ogm_cnt_lock); + orig_eq_count = orig_neigh_node->bcast_own_sum[if_incoming->if_num]; + neigh_rq_count = neigh_node->real_packet_count; + spin_unlock_bh(&orig_node->ogm_cnt_lock); + + /* pay attention to not get a value bigger than 100 % */ + if (orig_eq_count > neigh_rq_count) + total_count = neigh_rq_count; + else + total_count = orig_eq_count; + + /* if we have too few packets (too less data) we set tq_own to zero + * if we receive too few packets it is not considered bidirectional + */ + if (total_count < BATADV_TQ_LOCAL_BIDRECT_SEND_MINIMUM || + neigh_rq_count < BATADV_TQ_LOCAL_BIDRECT_RECV_MINIMUM) + tq_own = 0; + else + /* neigh_node->real_packet_count is never zero as we + * only purge old information when getting new + * information + */ + tq_own = (BATADV_TQ_MAX_VALUE * total_count) / neigh_rq_count; + + /* 1 - ((1-x) ** 3), normalized to TQ_MAX_VALUE this does + * affect the nearly-symmetric links only a little, but + * punishes asymmetric links more. This will give a value + * between 0 and TQ_MAX_VALUE + */ + neigh_rq_inv = BATADV_TQ_LOCAL_WINDOW_SIZE - neigh_rq_count; + neigh_rq_inv_cube = neigh_rq_inv * neigh_rq_inv * neigh_rq_inv; + neigh_rq_max_cube = BATADV_TQ_LOCAL_WINDOW_SIZE * + BATADV_TQ_LOCAL_WINDOW_SIZE * + BATADV_TQ_LOCAL_WINDOW_SIZE; + inv_asym_penalty = BATADV_TQ_MAX_VALUE * neigh_rq_inv_cube; + inv_asym_penalty /= neigh_rq_max_cube; + tq_asym_penalty = BATADV_TQ_MAX_VALUE - inv_asym_penalty; + + combined_tq = batadv_ogm_packet->tq * tq_own * tq_asym_penalty; + combined_tq /= BATADV_TQ_MAX_VALUE * BATADV_TQ_MAX_VALUE; + batadv_ogm_packet->tq = combined_tq; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "bidirectional: orig = %-15pM neigh = %-15pM => own_bcast = %2i, real recv = %2i, local tq: %3i, asym_penalty: %3i, total tq: %3i\n", + orig_node->orig, orig_neigh_node->orig, total_count, + neigh_rq_count, tq_own, + tq_asym_penalty, batadv_ogm_packet->tq); + + /* if link has the minimum required transmission quality + * consider it bidirectional + */ + if (batadv_ogm_packet->tq >= BATADV_TQ_TOTAL_BIDRECT_LIMIT) + ret = 1; + +out: + if (neigh_node) + batadv_neigh_node_free_ref(neigh_node); + return ret; +} + +/** + * batadv_iv_ogm_update_seqnos - process a batman packet for all interfaces, + * adjust the sequence number and find out whether it is a duplicate + * @ethhdr: ethernet header of the packet + * @batadv_ogm_packet: OGM packet to be considered + * @if_incoming: interface on which the OGM packet was received + * + * Returns duplicate status as enum batadv_dup_status + */ +static enum batadv_dup_status +batadv_iv_ogm_update_seqnos(const struct ethhdr *ethhdr, + const struct batadv_ogm_packet *batadv_ogm_packet, + const struct batadv_hard_iface *if_incoming) +{ + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_orig_node *orig_node; + struct batadv_neigh_node *tmp_neigh_node; + int is_dup; + int32_t seq_diff; + int need_update = 0; + int set_mark; + enum batadv_dup_status ret = BATADV_NO_DUP; + uint32_t seqno = ntohl(batadv_ogm_packet->seqno); + uint8_t *neigh_addr; + uint8_t packet_count; + + orig_node = batadv_get_orig_node(bat_priv, batadv_ogm_packet->orig); + if (!orig_node) + return BATADV_NO_DUP; + + spin_lock_bh(&orig_node->ogm_cnt_lock); + seq_diff = seqno - orig_node->last_real_seqno; + + /* signalize caller that the packet is to be dropped. */ + if (!hlist_empty(&orig_node->neigh_list) && + batadv_window_protected(bat_priv, seq_diff, + &orig_node->batman_seqno_reset)) { + ret = BATADV_PROTECTED; + goto out; + } + + rcu_read_lock(); + hlist_for_each_entry_rcu(tmp_neigh_node, + &orig_node->neigh_list, list) { + neigh_addr = tmp_neigh_node->addr; + is_dup = batadv_test_bit(tmp_neigh_node->real_bits, + orig_node->last_real_seqno, + seqno); + + if (batadv_compare_eth(neigh_addr, ethhdr->h_source) && + tmp_neigh_node->if_incoming == if_incoming) { + set_mark = 1; + if (is_dup) + ret = BATADV_NEIGH_DUP; + } else { + set_mark = 0; + if (is_dup && (ret != BATADV_NEIGH_DUP)) + ret = BATADV_ORIG_DUP; + } + + /* if the window moved, set the update flag. */ + need_update |= batadv_bit_get_packet(bat_priv, + tmp_neigh_node->real_bits, + seq_diff, set_mark); + + packet_count = bitmap_weight(tmp_neigh_node->real_bits, + BATADV_TQ_LOCAL_WINDOW_SIZE); + tmp_neigh_node->real_packet_count = packet_count; + } + rcu_read_unlock(); + + if (need_update) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "updating last_seqno: old %u, new %u\n", + orig_node->last_real_seqno, seqno); + orig_node->last_real_seqno = seqno; + } + +out: + spin_unlock_bh(&orig_node->ogm_cnt_lock); + batadv_orig_node_free_ref(orig_node); + return ret; +} + +static void batadv_iv_ogm_process(const struct ethhdr *ethhdr, + struct batadv_ogm_packet *batadv_ogm_packet, + const unsigned char *tt_buff, + struct batadv_hard_iface *if_incoming) +{ + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_hard_iface *hard_iface; + struct batadv_orig_node *orig_neigh_node, *orig_node; + struct batadv_neigh_node *router = NULL, *router_router = NULL; + struct batadv_neigh_node *orig_neigh_router = NULL; + int has_directlink_flag; + int is_my_addr = 0, is_my_orig = 0, is_my_oldorig = 0; + int is_broadcast = 0, is_bidirect; + bool is_single_hop_neigh = false; + bool is_from_best_next_hop = false; + int sameseq, similar_ttl; + enum batadv_dup_status dup_status; + uint32_t if_incoming_seqno; + uint8_t *prev_sender; + + /* Silently drop when the batman packet is actually not a + * correct packet. + * + * This might happen if a packet is padded (e.g. Ethernet has a + * minimum frame length of 64 byte) and the aggregation interprets + * it as an additional length. + * + * TODO: A more sane solution would be to have a bit in the + * batadv_ogm_packet to detect whether the packet is the last + * packet in an aggregation. Here we expect that the padding + * is always zero (or not 0x01) + */ + if (batadv_ogm_packet->header.packet_type != BATADV_IV_OGM) + return; + + /* could be changed by schedule_own_packet() */ + if_incoming_seqno = atomic_read(&if_incoming->bat_iv.ogm_seqno); + + if (batadv_ogm_packet->flags & BATADV_DIRECTLINK) + has_directlink_flag = 1; + else + has_directlink_flag = 0; + + if (batadv_compare_eth(ethhdr->h_source, batadv_ogm_packet->orig)) + is_single_hop_neigh = true; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Received BATMAN packet via NB: %pM, IF: %s [%pM] (from OG: %pM, via prev OG: %pM, seqno %u, ttvn %u, crc %#.4x, changes %u, tq %d, TTL %d, V %d, IDF %d)\n", + ethhdr->h_source, if_incoming->net_dev->name, + if_incoming->net_dev->dev_addr, batadv_ogm_packet->orig, + batadv_ogm_packet->prev_sender, + ntohl(batadv_ogm_packet->seqno), batadv_ogm_packet->ttvn, + ntohs(batadv_ogm_packet->tt_crc), + batadv_ogm_packet->tt_num_changes, batadv_ogm_packet->tq, + batadv_ogm_packet->header.ttl, + batadv_ogm_packet->header.version, has_directlink_flag); + + rcu_read_lock(); + list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { + if (hard_iface->if_status != BATADV_IF_ACTIVE) + continue; + + if (hard_iface->soft_iface != if_incoming->soft_iface) + continue; + + if (batadv_compare_eth(ethhdr->h_source, + hard_iface->net_dev->dev_addr)) + is_my_addr = 1; + + if (batadv_compare_eth(batadv_ogm_packet->orig, + hard_iface->net_dev->dev_addr)) + is_my_orig = 1; + + if (batadv_compare_eth(batadv_ogm_packet->prev_sender, + hard_iface->net_dev->dev_addr)) + is_my_oldorig = 1; + + if (is_broadcast_ether_addr(ethhdr->h_source)) + is_broadcast = 1; + } + rcu_read_unlock(); + + if (batadv_ogm_packet->header.version != BATADV_COMPAT_VERSION) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: incompatible batman version (%i)\n", + batadv_ogm_packet->header.version); + return; + } + + if (is_my_addr) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: received my own broadcast (sender: %pM)\n", + ethhdr->h_source); + return; + } + + if (is_broadcast) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: ignoring all packets with broadcast source addr (sender: %pM)\n", + ethhdr->h_source); + return; + } + + if (is_my_orig) { + unsigned long *word; + int offset; + int32_t bit_pos; + int16_t if_num; + uint8_t *weight; + + orig_neigh_node = batadv_get_orig_node(bat_priv, + ethhdr->h_source); + if (!orig_neigh_node) + return; + + /* neighbor has to indicate direct link and it has to + * come via the corresponding interface + * save packet seqno for bidirectional check + */ + if (has_directlink_flag && + batadv_compare_eth(if_incoming->net_dev->dev_addr, + batadv_ogm_packet->orig)) { + if_num = if_incoming->if_num; + offset = if_num * BATADV_NUM_WORDS; + + spin_lock_bh(&orig_neigh_node->ogm_cnt_lock); + word = &(orig_neigh_node->bcast_own[offset]); + bit_pos = if_incoming_seqno - 2; + bit_pos -= ntohl(batadv_ogm_packet->seqno); + batadv_set_bit(word, bit_pos); + weight = &orig_neigh_node->bcast_own_sum[if_num]; + *weight = bitmap_weight(word, + BATADV_TQ_LOCAL_WINDOW_SIZE); + spin_unlock_bh(&orig_neigh_node->ogm_cnt_lock); + } + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: originator packet from myself (via neighbor)\n"); + batadv_orig_node_free_ref(orig_neigh_node); + return; + } + + if (is_my_oldorig) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: ignoring all rebroadcast echos (sender: %pM)\n", + ethhdr->h_source); + return; + } + + if (batadv_ogm_packet->flags & BATADV_NOT_BEST_NEXT_HOP) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: ignoring all packets not forwarded from the best next hop (sender: %pM)\n", + ethhdr->h_source); + return; + } + + orig_node = batadv_get_orig_node(bat_priv, batadv_ogm_packet->orig); + if (!orig_node) + return; + + dup_status = batadv_iv_ogm_update_seqnos(ethhdr, batadv_ogm_packet, + if_incoming); + + if (dup_status == BATADV_PROTECTED) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: packet within seqno protection time (sender: %pM)\n", + ethhdr->h_source); + goto out; + } + + if (batadv_ogm_packet->tq == 0) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: originator packet with tq equal 0\n"); + goto out; + } + + router = batadv_orig_node_get_router(orig_node); + if (router) + router_router = batadv_orig_node_get_router(router->orig_node); + + if ((router && router->tq_avg != 0) && + (batadv_compare_eth(router->addr, ethhdr->h_source))) + is_from_best_next_hop = true; + + prev_sender = batadv_ogm_packet->prev_sender; + /* avoid temporary routing loops */ + if (router && router_router && + (batadv_compare_eth(router->addr, prev_sender)) && + !(batadv_compare_eth(batadv_ogm_packet->orig, prev_sender)) && + (batadv_compare_eth(router->addr, router_router->addr))) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: ignoring all rebroadcast packets that may make me loop (sender: %pM)\n", + ethhdr->h_source); + goto out; + } + + /* if sender is a direct neighbor the sender mac equals + * originator mac + */ + if (is_single_hop_neigh) + orig_neigh_node = orig_node; + else + orig_neigh_node = batadv_get_orig_node(bat_priv, + ethhdr->h_source); + + if (!orig_neigh_node) + goto out; + + /* Update nc_nodes of the originator */ + batadv_nc_update_nc_node(bat_priv, orig_node, orig_neigh_node, + batadv_ogm_packet, is_single_hop_neigh); + + orig_neigh_router = batadv_orig_node_get_router(orig_neigh_node); + + /* drop packet if sender is not a direct neighbor and if we + * don't route towards it + */ + if (!is_single_hop_neigh && (!orig_neigh_router)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: OGM via unknown neighbor!\n"); + goto out_neigh; + } + + is_bidirect = batadv_iv_ogm_calc_tq(orig_node, orig_neigh_node, + batadv_ogm_packet, if_incoming); + + batadv_bonding_save_primary(orig_node, orig_neigh_node, + batadv_ogm_packet); + + /* update ranking if it is not a duplicate or has the same + * seqno and similar ttl as the non-duplicate + */ + sameseq = orig_node->last_real_seqno == ntohl(batadv_ogm_packet->seqno); + similar_ttl = orig_node->last_ttl - 3 <= batadv_ogm_packet->header.ttl; + if (is_bidirect && ((dup_status == BATADV_NO_DUP) || + (sameseq && similar_ttl))) + batadv_iv_ogm_orig_update(bat_priv, orig_node, ethhdr, + batadv_ogm_packet, if_incoming, + tt_buff, dup_status); + + /* is single hop (direct) neighbor */ + if (is_single_hop_neigh) { + /* mark direct link on incoming interface */ + batadv_iv_ogm_forward(orig_node, ethhdr, batadv_ogm_packet, + is_single_hop_neigh, + is_from_best_next_hop, if_incoming); + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Forwarding packet: rebroadcast neighbor packet with direct link flag\n"); + goto out_neigh; + } + + /* multihop originator */ + if (!is_bidirect) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: not received via bidirectional link\n"); + goto out_neigh; + } + + if (dup_status == BATADV_NEIGH_DUP) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: duplicate packet received\n"); + goto out_neigh; + } + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Forwarding packet: rebroadcast originator packet\n"); + batadv_iv_ogm_forward(orig_node, ethhdr, batadv_ogm_packet, + is_single_hop_neigh, is_from_best_next_hop, + if_incoming); + +out_neigh: + if ((orig_neigh_node) && (!is_single_hop_neigh)) + batadv_orig_node_free_ref(orig_neigh_node); +out: + if (router) + batadv_neigh_node_free_ref(router); + if (router_router) + batadv_neigh_node_free_ref(router_router); + if (orig_neigh_router) + batadv_neigh_node_free_ref(orig_neigh_router); + + batadv_orig_node_free_ref(orig_node); +} + +static int batadv_iv_ogm_receive(struct sk_buff *skb, + struct batadv_hard_iface *if_incoming) +{ + struct batadv_priv *bat_priv = netdev_priv(if_incoming->soft_iface); + struct batadv_ogm_packet *batadv_ogm_packet; + struct ethhdr *ethhdr; + int buff_pos = 0, packet_len; + unsigned char *tt_buff, *packet_buff; + bool ret; + uint8_t *packet_pos; + + ret = batadv_check_management_packet(skb, if_incoming, BATADV_OGM_HLEN); + if (!ret) + return NET_RX_DROP; + + /* did we receive a B.A.T.M.A.N. IV OGM packet on an interface + * that does not have B.A.T.M.A.N. IV enabled ? + */ + if (bat_priv->bat_algo_ops->bat_ogm_emit != batadv_iv_ogm_emit) + return NET_RX_DROP; + + batadv_inc_counter(bat_priv, BATADV_CNT_MGMT_RX); + batadv_add_counter(bat_priv, BATADV_CNT_MGMT_RX_BYTES, + skb->len + ETH_HLEN); + + packet_len = skb_headlen(skb); + ethhdr = (struct ethhdr *)skb_mac_header(skb); + packet_buff = skb->data; + batadv_ogm_packet = (struct batadv_ogm_packet *)packet_buff; + + /* unpack the aggregated packets and process them one by one */ + while (batadv_iv_ogm_aggr_packet(buff_pos, packet_len, + batadv_ogm_packet->tt_num_changes)) { + tt_buff = packet_buff + buff_pos + BATADV_OGM_HLEN; + + batadv_iv_ogm_process(ethhdr, batadv_ogm_packet, tt_buff, + if_incoming); + + buff_pos += BATADV_OGM_HLEN; + buff_pos += batadv_tt_len(batadv_ogm_packet->tt_num_changes); + + packet_pos = packet_buff + buff_pos; + batadv_ogm_packet = (struct batadv_ogm_packet *)packet_pos; + } + + kfree_skb(skb); + return NET_RX_SUCCESS; +} + +static struct batadv_algo_ops batadv_batman_iv __read_mostly = { + .name = "BATMAN_IV", + .bat_iface_enable = batadv_iv_ogm_iface_enable, + .bat_iface_disable = batadv_iv_ogm_iface_disable, + .bat_iface_update_mac = batadv_iv_ogm_iface_update_mac, + .bat_primary_iface_set = batadv_iv_ogm_primary_iface_set, + .bat_ogm_schedule = batadv_iv_ogm_schedule, + .bat_ogm_emit = batadv_iv_ogm_emit, +}; + +int __init batadv_iv_init(void) +{ + int ret; + + /* batman originator packet */ + ret = batadv_recv_handler_register(BATADV_IV_OGM, + batadv_iv_ogm_receive); + if (ret < 0) + goto out; + + ret = batadv_algo_register(&batadv_batman_iv); + if (ret < 0) + goto handler_unregister; + + goto out; + +handler_unregister: + batadv_recv_handler_unregister(BATADV_IV_OGM); +out: + return ret; +} diff --git a/net/batman-adv/bitarray.c b/net/batman-adv/bitarray.c new file mode 100644 index 00000000..97398241 --- /dev/null +++ b/net/batman-adv/bitarray.c @@ -0,0 +1,95 @@ +/* Copyright (C) 2006-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich, Marek Lindner + * + * 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 "bitarray.h" + +#include <linux/bitops.h> + +/* shift the packet array by n places. */ +static void batadv_bitmap_shift_left(unsigned long *seq_bits, int32_t n) +{ + if (n <= 0 || n >= BATADV_TQ_LOCAL_WINDOW_SIZE) + return; + + bitmap_shift_left(seq_bits, seq_bits, n, BATADV_TQ_LOCAL_WINDOW_SIZE); +} + + +/* receive and process one packet within the sequence number window. + * + * returns: + * 1 if the window was moved (either new or very old) + * 0 if the window was not moved/shifted. + */ +int batadv_bit_get_packet(void *priv, unsigned long *seq_bits, + int32_t seq_num_diff, int set_mark) +{ + struct batadv_priv *bat_priv = priv; + + /* sequence number is slightly older. We already got a sequence number + * higher than this one, so we just mark it. + */ + if (seq_num_diff <= 0 && seq_num_diff > -BATADV_TQ_LOCAL_WINDOW_SIZE) { + if (set_mark) + batadv_set_bit(seq_bits, -seq_num_diff); + return 0; + } + + /* sequence number is slightly newer, so we shift the window and + * set the mark if required + */ + if (seq_num_diff > 0 && seq_num_diff < BATADV_TQ_LOCAL_WINDOW_SIZE) { + batadv_bitmap_shift_left(seq_bits, seq_num_diff); + + if (set_mark) + batadv_set_bit(seq_bits, 0); + return 1; + } + + /* sequence number is much newer, probably missed a lot of packets */ + if (seq_num_diff >= BATADV_TQ_LOCAL_WINDOW_SIZE && + seq_num_diff < BATADV_EXPECTED_SEQNO_RANGE) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "We missed a lot of packets (%i) !\n", + seq_num_diff - 1); + bitmap_zero(seq_bits, BATADV_TQ_LOCAL_WINDOW_SIZE); + if (set_mark) + batadv_set_bit(seq_bits, 0); + return 1; + } + + /* received a much older packet. The other host either restarted + * or the old packet got delayed somewhere in the network. The + * packet should be dropped without calling this function if the + * seqno window is protected. + * + * seq_num_diff <= -BATADV_TQ_LOCAL_WINDOW_SIZE + * or + * seq_num_diff >= BATADV_EXPECTED_SEQNO_RANGE + */ + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Other host probably restarted!\n"); + + bitmap_zero(seq_bits, BATADV_TQ_LOCAL_WINDOW_SIZE); + if (set_mark) + batadv_set_bit(seq_bits, 0); + + return 1; +} diff --git a/net/batman-adv/bitarray.h b/net/batman-adv/bitarray.h new file mode 100644 index 00000000..a81b9322 --- /dev/null +++ b/net/batman-adv/bitarray.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2006-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich, Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_BITARRAY_H_ +#define _NET_BATMAN_ADV_BITARRAY_H_ + +/* Returns 1 if the corresponding bit in the given seq_bits indicates true + * and curr_seqno is within range of last_seqno. Otherwise returns 0. + */ +static inline int batadv_test_bit(const unsigned long *seq_bits, + uint32_t last_seqno, uint32_t curr_seqno) +{ + int32_t diff; + + diff = last_seqno - curr_seqno; + if (diff < 0 || diff >= BATADV_TQ_LOCAL_WINDOW_SIZE) + return 0; + else + return test_bit(diff, seq_bits) != 0; +} + +/* turn corresponding bit on, so we can remember that we got the packet */ +static inline void batadv_set_bit(unsigned long *seq_bits, int32_t n) +{ + /* if too old, just drop it */ + if (n < 0 || n >= BATADV_TQ_LOCAL_WINDOW_SIZE) + return; + + set_bit(n, seq_bits); /* turn the position on */ +} + +/* receive and process one packet, returns 1 if received seq_num is considered + * new, 0 if old + */ +int batadv_bit_get_packet(void *priv, unsigned long *seq_bits, + int32_t seq_num_diff, int set_mark); + +#endif /* _NET_BATMAN_ADV_BITARRAY_H_ */ diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c new file mode 100644 index 00000000..de27b317 --- /dev/null +++ b/net/batman-adv/bridge_loop_avoidance.c @@ -0,0 +1,1694 @@ +/* Copyright (C) 2011-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich + * + * 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 "hash.h" +#include "hard-interface.h" +#include "originator.h" +#include "bridge_loop_avoidance.h" +#include "translation-table.h" +#include "send.h" + +#include <linux/etherdevice.h> +#include <linux/crc16.h> +#include <linux/if_arp.h> +#include <net/arp.h> +#include <linux/if_vlan.h> + +static const uint8_t batadv_announce_mac[4] = {0x43, 0x05, 0x43, 0x05}; + +static void batadv_bla_periodic_work(struct work_struct *work); +static void +batadv_bla_send_announce(struct batadv_priv *bat_priv, + struct batadv_bla_backbone_gw *backbone_gw); + +/* return the index of the claim */ +static inline uint32_t batadv_choose_claim(const void *data, uint32_t size) +{ + struct batadv_bla_claim *claim = (struct batadv_bla_claim *)data; + uint32_t hash = 0; + + hash = batadv_hash_bytes(hash, &claim->addr, sizeof(claim->addr)); + hash = batadv_hash_bytes(hash, &claim->vid, sizeof(claim->vid)); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash % size; +} + +/* return the index of the backbone gateway */ +static inline uint32_t batadv_choose_backbone_gw(const void *data, + uint32_t size) +{ + const struct batadv_bla_claim *claim = (struct batadv_bla_claim *)data; + uint32_t hash = 0; + + hash = batadv_hash_bytes(hash, &claim->addr, sizeof(claim->addr)); + hash = batadv_hash_bytes(hash, &claim->vid, sizeof(claim->vid)); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash % size; +} + + +/* compares address and vid of two backbone gws */ +static int batadv_compare_backbone_gw(const struct hlist_node *node, + const void *data2) +{ + const void *data1 = container_of(node, struct batadv_bla_backbone_gw, + hash_entry); + const struct batadv_bla_backbone_gw *gw1 = data1, *gw2 = data2; + + if (!batadv_compare_eth(gw1->orig, gw2->orig)) + return 0; + + if (gw1->vid != gw2->vid) + return 0; + + return 1; +} + +/* compares address and vid of two claims */ +static int batadv_compare_claim(const struct hlist_node *node, + const void *data2) +{ + const void *data1 = container_of(node, struct batadv_bla_claim, + hash_entry); + const struct batadv_bla_claim *cl1 = data1, *cl2 = data2; + + if (!batadv_compare_eth(cl1->addr, cl2->addr)) + return 0; + + if (cl1->vid != cl2->vid) + return 0; + + return 1; +} + +/* free a backbone gw */ +static void +batadv_backbone_gw_free_ref(struct batadv_bla_backbone_gw *backbone_gw) +{ + if (atomic_dec_and_test(&backbone_gw->refcount)) + kfree_rcu(backbone_gw, rcu); +} + +/* finally deinitialize the claim */ +static void batadv_claim_free_rcu(struct rcu_head *rcu) +{ + struct batadv_bla_claim *claim; + + claim = container_of(rcu, struct batadv_bla_claim, rcu); + + batadv_backbone_gw_free_ref(claim->backbone_gw); + kfree(claim); +} + +/* free a claim, call claim_free_rcu if its the last reference */ +static void batadv_claim_free_ref(struct batadv_bla_claim *claim) +{ + if (atomic_dec_and_test(&claim->refcount)) + call_rcu(&claim->rcu, batadv_claim_free_rcu); +} + +/* @bat_priv: the bat priv with all the soft interface information + * @data: search data (may be local/static data) + * + * looks for a claim in the hash, and returns it if found + * or NULL otherwise. + */ +static struct batadv_bla_claim +*batadv_claim_hash_find(struct batadv_priv *bat_priv, + struct batadv_bla_claim *data) +{ + struct batadv_hashtable *hash = bat_priv->bla.claim_hash; + struct hlist_head *head; + struct batadv_bla_claim *claim; + struct batadv_bla_claim *claim_tmp = NULL; + int index; + + if (!hash) + return NULL; + + index = batadv_choose_claim(data, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(claim, head, hash_entry) { + if (!batadv_compare_claim(&claim->hash_entry, data)) + continue; + + if (!atomic_inc_not_zero(&claim->refcount)) + continue; + + claim_tmp = claim; + break; + } + rcu_read_unlock(); + + return claim_tmp; +} + +/** + * batadv_backbone_hash_find - looks for a claim in the hash + * @bat_priv: the bat priv with all the soft interface information + * @addr: the address of the originator + * @vid: the VLAN ID + * + * Returns claim if found or NULL otherwise. + */ +static struct batadv_bla_backbone_gw * +batadv_backbone_hash_find(struct batadv_priv *bat_priv, + uint8_t *addr, short vid) +{ + struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; + struct hlist_head *head; + struct batadv_bla_backbone_gw search_entry, *backbone_gw; + struct batadv_bla_backbone_gw *backbone_gw_tmp = NULL; + int index; + + if (!hash) + return NULL; + + memcpy(search_entry.orig, addr, ETH_ALEN); + search_entry.vid = vid; + + index = batadv_choose_backbone_gw(&search_entry, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(backbone_gw, head, hash_entry) { + if (!batadv_compare_backbone_gw(&backbone_gw->hash_entry, + &search_entry)) + continue; + + if (!atomic_inc_not_zero(&backbone_gw->refcount)) + continue; + + backbone_gw_tmp = backbone_gw; + break; + } + rcu_read_unlock(); + + return backbone_gw_tmp; +} + +/* delete all claims for a backbone */ +static void +batadv_bla_del_backbone_claims(struct batadv_bla_backbone_gw *backbone_gw) +{ + struct batadv_hashtable *hash; + struct hlist_node *node_tmp; + struct hlist_head *head; + struct batadv_bla_claim *claim; + int i; + spinlock_t *list_lock; /* protects write access to the hash lists */ + + hash = backbone_gw->bat_priv->bla.claim_hash; + 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(claim, node_tmp, + head, hash_entry) { + if (claim->backbone_gw != backbone_gw) + continue; + + batadv_claim_free_ref(claim); + hlist_del_rcu(&claim->hash_entry); + } + spin_unlock_bh(list_lock); + } + + /* all claims gone, intialize CRC */ + backbone_gw->crc = BATADV_BLA_CRC_INIT; +} + +/** + * batadv_bla_send_claim - sends a claim frame according to the provided info + * @bat_priv: the bat priv with all the soft interface information + * @orig: the mac address to be announced within the claim + * @vid: the VLAN ID + * @claimtype: the type of the claim (CLAIM, UNCLAIM, ANNOUNCE, ...) + */ +static void batadv_bla_send_claim(struct batadv_priv *bat_priv, uint8_t *mac, + short vid, int claimtype) +{ + struct sk_buff *skb; + struct ethhdr *ethhdr; + struct batadv_hard_iface *primary_if; + struct net_device *soft_iface; + uint8_t *hw_src; + struct batadv_bla_claim_dst local_claim_dest; + __be32 zeroip = 0; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + return; + + memcpy(&local_claim_dest, &bat_priv->bla.claim_dest, + sizeof(local_claim_dest)); + local_claim_dest.type = claimtype; + + soft_iface = primary_if->soft_iface; + + skb = arp_create(ARPOP_REPLY, ETH_P_ARP, + /* IP DST: 0.0.0.0 */ + zeroip, + primary_if->soft_iface, + /* IP SRC: 0.0.0.0 */ + zeroip, + /* Ethernet DST: Broadcast */ + NULL, + /* Ethernet SRC/HW SRC: originator mac */ + primary_if->net_dev->dev_addr, + /* HW DST: FF:43:05:XX:YY:YY + * with XX = claim type + * and YY:YY = group id + */ + (uint8_t *)&local_claim_dest); + + if (!skb) + goto out; + + ethhdr = (struct ethhdr *)skb->data; + hw_src = (uint8_t *)ethhdr + ETH_HLEN + sizeof(struct arphdr); + + /* now we pretend that the client would have sent this ... */ + switch (claimtype) { + case BATADV_CLAIM_TYPE_CLAIM: + /* normal claim frame + * set Ethernet SRC to the clients mac + */ + memcpy(ethhdr->h_source, mac, ETH_ALEN); + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_send_claim(): CLAIM %pM on vid %d\n", mac, vid); + break; + case BATADV_CLAIM_TYPE_UNCLAIM: + /* unclaim frame + * set HW SRC to the clients mac + */ + memcpy(hw_src, mac, ETH_ALEN); + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_send_claim(): UNCLAIM %pM on vid %d\n", mac, + vid); + break; + case BATADV_CLAIM_TYPE_ANNOUNCE: + /* announcement frame + * set HW SRC to the special mac containg the crc + */ + memcpy(hw_src, mac, ETH_ALEN); + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_send_claim(): ANNOUNCE of %pM on vid %d\n", + ethhdr->h_source, vid); + break; + case BATADV_CLAIM_TYPE_REQUEST: + /* request frame + * set HW SRC and header destination to the receiving backbone + * gws mac + */ + memcpy(hw_src, mac, ETH_ALEN); + memcpy(ethhdr->h_dest, mac, ETH_ALEN); + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_send_claim(): REQUEST of %pM to %pMon vid %d\n", + ethhdr->h_source, ethhdr->h_dest, vid); + break; + } + + if (vid != -1) + skb = vlan_insert_tag(skb, htons(ETH_P_8021Q), vid); + + skb_reset_mac_header(skb); + skb->protocol = eth_type_trans(skb, soft_iface); + batadv_inc_counter(bat_priv, BATADV_CNT_RX); + batadv_add_counter(bat_priv, BATADV_CNT_RX_BYTES, + skb->len + ETH_HLEN); + soft_iface->last_rx = jiffies; + + netif_rx(skb); +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); +} + +/** + * batadv_bla_get_backbone_gw + * @bat_priv: the bat priv with all the soft interface information + * @orig: the mac address of the originator + * @vid: the VLAN ID + * + * searches for the backbone gw or creates a new one if it could not + * be found. + */ +static struct batadv_bla_backbone_gw * +batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, uint8_t *orig, + short vid, bool own_backbone) +{ + struct batadv_bla_backbone_gw *entry; + struct batadv_orig_node *orig_node; + int hash_added; + + entry = batadv_backbone_hash_find(bat_priv, orig, vid); + + if (entry) + return entry; + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_get_backbone_gw(): not found (%pM, %d), creating new entry\n", + orig, vid); + + entry = kzalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) + return NULL; + + entry->vid = vid; + entry->lasttime = jiffies; + entry->crc = BATADV_BLA_CRC_INIT; + entry->bat_priv = bat_priv; + atomic_set(&entry->request_sent, 0); + atomic_set(&entry->wait_periods, 0); + memcpy(entry->orig, orig, ETH_ALEN); + + /* one for the hash, one for returning */ + atomic_set(&entry->refcount, 2); + + hash_added = batadv_hash_add(bat_priv->bla.backbone_hash, + batadv_compare_backbone_gw, + batadv_choose_backbone_gw, entry, + &entry->hash_entry); + + if (unlikely(hash_added != 0)) { + /* hash failed, free the structure */ + kfree(entry); + return NULL; + } + + /* this is a gateway now, remove any tt entries */ + orig_node = batadv_orig_hash_find(bat_priv, orig); + if (orig_node) { + batadv_tt_global_del_orig(bat_priv, orig_node, + "became a backbone gateway"); + batadv_orig_node_free_ref(orig_node); + } + + if (own_backbone) { + batadv_bla_send_announce(bat_priv, entry); + + /* this will be decreased in the worker thread */ + atomic_inc(&entry->request_sent); + atomic_set(&entry->wait_periods, BATADV_BLA_WAIT_PERIODS); + atomic_inc(&bat_priv->bla.num_requests); + } + + return entry; +} + +/* update or add the own backbone gw to make sure we announce + * where we receive other backbone gws + */ +static void +batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + short vid) +{ + struct batadv_bla_backbone_gw *backbone_gw; + + backbone_gw = batadv_bla_get_backbone_gw(bat_priv, + primary_if->net_dev->dev_addr, + vid, true); + if (unlikely(!backbone_gw)) + return; + + backbone_gw->lasttime = jiffies; + batadv_backbone_gw_free_ref(backbone_gw); +} + +/* @bat_priv: the bat priv with all the soft interface information + * @vid: the vid where the request came on + * + * Repeat all of our own claims, and finally send an ANNOUNCE frame + * to allow the requester another check if the CRC is correct now. + */ +static void batadv_bla_answer_request(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + short vid) +{ + struct hlist_head *head; + struct batadv_hashtable *hash; + struct batadv_bla_claim *claim; + struct batadv_bla_backbone_gw *backbone_gw; + int i; + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_answer_request(): received a claim request, send all of our own claims again\n"); + + backbone_gw = batadv_backbone_hash_find(bat_priv, + primary_if->net_dev->dev_addr, + vid); + if (!backbone_gw) + return; + + hash = bat_priv->bla.claim_hash; + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(claim, head, hash_entry) { + /* only own claims are interesting */ + if (claim->backbone_gw != backbone_gw) + continue; + + batadv_bla_send_claim(bat_priv, claim->addr, claim->vid, + BATADV_CLAIM_TYPE_CLAIM); + } + rcu_read_unlock(); + } + + /* finally, send an announcement frame */ + batadv_bla_send_announce(bat_priv, backbone_gw); + batadv_backbone_gw_free_ref(backbone_gw); +} + +/* @backbone_gw: the backbone gateway from whom we are out of sync + * + * When the crc is wrong, ask the backbone gateway for a full table update. + * After the request, it will repeat all of his own claims and finally + * send an announcement claim with which we can check again. + */ +static void batadv_bla_send_request(struct batadv_bla_backbone_gw *backbone_gw) +{ + /* first, remove all old entries */ + batadv_bla_del_backbone_claims(backbone_gw); + + batadv_dbg(BATADV_DBG_BLA, backbone_gw->bat_priv, + "Sending REQUEST to %pM\n", backbone_gw->orig); + + /* send request */ + batadv_bla_send_claim(backbone_gw->bat_priv, backbone_gw->orig, + backbone_gw->vid, BATADV_CLAIM_TYPE_REQUEST); + + /* no local broadcasts should be sent or received, for now. */ + if (!atomic_read(&backbone_gw->request_sent)) { + atomic_inc(&backbone_gw->bat_priv->bla.num_requests); + atomic_set(&backbone_gw->request_sent, 1); + } +} + +/* @bat_priv: the bat priv with all the soft interface information + * @backbone_gw: our backbone gateway which should be announced + * + * This function sends an announcement. It is called from multiple + * places. + */ +static void batadv_bla_send_announce(struct batadv_priv *bat_priv, + struct batadv_bla_backbone_gw *backbone_gw) +{ + uint8_t mac[ETH_ALEN]; + __be16 crc; + + memcpy(mac, batadv_announce_mac, 4); + crc = htons(backbone_gw->crc); + memcpy(&mac[4], &crc, 2); + + batadv_bla_send_claim(bat_priv, mac, backbone_gw->vid, + BATADV_CLAIM_TYPE_ANNOUNCE); +} + +/** + * batadv_bla_add_claim - Adds a claim in the claim hash + * @bat_priv: the bat priv with all the soft interface information + * @mac: the mac address of the claim + * @vid: the VLAN ID of the frame + * @backbone_gw: the backbone gateway which claims it + */ +static void batadv_bla_add_claim(struct batadv_priv *bat_priv, + const uint8_t *mac, const short vid, + struct batadv_bla_backbone_gw *backbone_gw) +{ + struct batadv_bla_claim *claim; + struct batadv_bla_claim search_claim; + int hash_added; + + memcpy(search_claim.addr, mac, ETH_ALEN); + search_claim.vid = vid; + claim = batadv_claim_hash_find(bat_priv, &search_claim); + + /* create a new claim entry if it does not exist yet. */ + if (!claim) { + claim = kzalloc(sizeof(*claim), GFP_ATOMIC); + if (!claim) + return; + + memcpy(claim->addr, mac, ETH_ALEN); + claim->vid = vid; + claim->lasttime = jiffies; + claim->backbone_gw = backbone_gw; + + atomic_set(&claim->refcount, 2); + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_add_claim(): adding new entry %pM, vid %d to hash ...\n", + mac, vid); + hash_added = batadv_hash_add(bat_priv->bla.claim_hash, + batadv_compare_claim, + batadv_choose_claim, claim, + &claim->hash_entry); + + if (unlikely(hash_added != 0)) { + /* only local changes happened. */ + kfree(claim); + return; + } + } else { + claim->lasttime = jiffies; + if (claim->backbone_gw == backbone_gw) + /* no need to register a new backbone */ + goto claim_free_ref; + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_add_claim(): changing ownership for %pM, vid %d\n", + mac, vid); + + claim->backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN); + batadv_backbone_gw_free_ref(claim->backbone_gw); + } + /* set (new) backbone gw */ + atomic_inc(&backbone_gw->refcount); + claim->backbone_gw = backbone_gw; + + backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN); + backbone_gw->lasttime = jiffies; + +claim_free_ref: + batadv_claim_free_ref(claim); +} + +/* Delete a claim from the claim hash which has the + * given mac address and vid. + */ +static void batadv_bla_del_claim(struct batadv_priv *bat_priv, + const uint8_t *mac, const short vid) +{ + struct batadv_bla_claim search_claim, *claim; + + memcpy(search_claim.addr, mac, ETH_ALEN); + search_claim.vid = vid; + claim = batadv_claim_hash_find(bat_priv, &search_claim); + if (!claim) + return; + + batadv_dbg(BATADV_DBG_BLA, bat_priv, "bla_del_claim(): %pM, vid %d\n", + mac, vid); + + batadv_hash_remove(bat_priv->bla.claim_hash, batadv_compare_claim, + batadv_choose_claim, claim); + batadv_claim_free_ref(claim); /* reference from the hash is gone */ + + claim->backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN); + + /* don't need the reference from hash_find() anymore */ + batadv_claim_free_ref(claim); +} + +/* check for ANNOUNCE frame, return 1 if handled */ +static int batadv_handle_announce(struct batadv_priv *bat_priv, + uint8_t *an_addr, uint8_t *backbone_addr, + short vid) +{ + struct batadv_bla_backbone_gw *backbone_gw; + uint16_t crc; + + if (memcmp(an_addr, batadv_announce_mac, 4) != 0) + return 0; + + backbone_gw = batadv_bla_get_backbone_gw(bat_priv, backbone_addr, vid, + false); + + if (unlikely(!backbone_gw)) + return 1; + + + /* handle as ANNOUNCE frame */ + backbone_gw->lasttime = jiffies; + crc = ntohs(*((__be16 *)(&an_addr[4]))); + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "handle_announce(): ANNOUNCE vid %d (sent by %pM)... CRC = %#.4x\n", + vid, backbone_gw->orig, crc); + + if (backbone_gw->crc != crc) { + batadv_dbg(BATADV_DBG_BLA, backbone_gw->bat_priv, + "handle_announce(): CRC FAILED for %pM/%d (my = %#.4x, sent = %#.4x)\n", + backbone_gw->orig, backbone_gw->vid, + backbone_gw->crc, crc); + + batadv_bla_send_request(backbone_gw); + } else { + /* if we have sent a request and the crc was OK, + * we can allow traffic again. + */ + if (atomic_read(&backbone_gw->request_sent)) { + atomic_dec(&backbone_gw->bat_priv->bla.num_requests); + atomic_set(&backbone_gw->request_sent, 0); + } + } + + batadv_backbone_gw_free_ref(backbone_gw); + return 1; +} + +/* check for REQUEST frame, return 1 if handled */ +static int batadv_handle_request(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + uint8_t *backbone_addr, + struct ethhdr *ethhdr, short vid) +{ + /* check for REQUEST frame */ + if (!batadv_compare_eth(backbone_addr, ethhdr->h_dest)) + return 0; + + /* sanity check, this should not happen on a normal switch, + * we ignore it in this case. + */ + if (!batadv_compare_eth(ethhdr->h_dest, primary_if->net_dev->dev_addr)) + return 1; + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "handle_request(): REQUEST vid %d (sent by %pM)...\n", + vid, ethhdr->h_source); + + batadv_bla_answer_request(bat_priv, primary_if, vid); + return 1; +} + +/* check for UNCLAIM frame, return 1 if handled */ +static int batadv_handle_unclaim(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + uint8_t *backbone_addr, + uint8_t *claim_addr, short vid) +{ + struct batadv_bla_backbone_gw *backbone_gw; + + /* unclaim in any case if it is our own */ + if (primary_if && batadv_compare_eth(backbone_addr, + primary_if->net_dev->dev_addr)) + batadv_bla_send_claim(bat_priv, claim_addr, vid, + BATADV_CLAIM_TYPE_UNCLAIM); + + backbone_gw = batadv_backbone_hash_find(bat_priv, backbone_addr, vid); + + if (!backbone_gw) + return 1; + + /* this must be an UNCLAIM frame */ + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "handle_unclaim(): UNCLAIM %pM on vid %d (sent by %pM)...\n", + claim_addr, vid, backbone_gw->orig); + + batadv_bla_del_claim(bat_priv, claim_addr, vid); + batadv_backbone_gw_free_ref(backbone_gw); + return 1; +} + +/* check for CLAIM frame, return 1 if handled */ +static int batadv_handle_claim(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + uint8_t *backbone_addr, uint8_t *claim_addr, + short vid) +{ + struct batadv_bla_backbone_gw *backbone_gw; + + /* register the gateway if not yet available, and add the claim. */ + + backbone_gw = batadv_bla_get_backbone_gw(bat_priv, backbone_addr, vid, + false); + + if (unlikely(!backbone_gw)) + return 1; + + /* this must be a CLAIM frame */ + batadv_bla_add_claim(bat_priv, claim_addr, vid, backbone_gw); + if (batadv_compare_eth(backbone_addr, primary_if->net_dev->dev_addr)) + batadv_bla_send_claim(bat_priv, claim_addr, vid, + BATADV_CLAIM_TYPE_CLAIM); + + /* TODO: we could call something like tt_local_del() here. */ + + batadv_backbone_gw_free_ref(backbone_gw); + return 1; +} + +/** + * batadv_check_claim_group + * @bat_priv: the bat priv with all the soft interface information + * @hw_src: the Hardware source in the ARP Header + * @hw_dst: the Hardware destination in the ARP Header + * @ethhdr: pointer to the Ethernet header of the claim frame + * + * checks if it is a claim packet and if its on the same group. + * This function also applies the group ID of the sender + * if it is in the same mesh. + * + * returns: + * 2 - if it is a claim packet and on the same group + * 1 - if is a claim packet from another group + * 0 - if it is not a claim packet + */ +static int batadv_check_claim_group(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + uint8_t *hw_src, uint8_t *hw_dst, + struct ethhdr *ethhdr) +{ + uint8_t *backbone_addr; + struct batadv_orig_node *orig_node; + struct batadv_bla_claim_dst *bla_dst, *bla_dst_own; + + bla_dst = (struct batadv_bla_claim_dst *)hw_dst; + bla_dst_own = &bat_priv->bla.claim_dest; + + /* check if it is a claim packet in general */ + if (memcmp(bla_dst->magic, bla_dst_own->magic, + sizeof(bla_dst->magic)) != 0) + return 0; + + /* if announcement packet, use the source, + * otherwise assume it is in the hw_src + */ + switch (bla_dst->type) { + case BATADV_CLAIM_TYPE_CLAIM: + backbone_addr = hw_src; + break; + case BATADV_CLAIM_TYPE_REQUEST: + case BATADV_CLAIM_TYPE_ANNOUNCE: + case BATADV_CLAIM_TYPE_UNCLAIM: + backbone_addr = ethhdr->h_source; + break; + default: + return 0; + } + + /* don't accept claim frames from ourselves */ + if (batadv_compare_eth(backbone_addr, primary_if->net_dev->dev_addr)) + return 0; + + /* if its already the same group, it is fine. */ + if (bla_dst->group == bla_dst_own->group) + return 2; + + /* lets see if this originator is in our mesh */ + orig_node = batadv_orig_hash_find(bat_priv, backbone_addr); + + /* dont accept claims from gateways which are not in + * the same mesh or group. + */ + if (!orig_node) + return 1; + + /* if our mesh friends mac is bigger, use it for ourselves. */ + if (ntohs(bla_dst->group) > ntohs(bla_dst_own->group)) { + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "taking other backbones claim group: %#.4x\n", + ntohs(bla_dst->group)); + bla_dst_own->group = bla_dst->group; + } + + batadv_orig_node_free_ref(orig_node); + + return 2; +} + + +/* @bat_priv: the bat priv with all the soft interface information + * @skb: the frame to be checked + * + * Check if this is a claim frame, and process it accordingly. + * + * returns 1 if it was a claim frame, otherwise return 0 to + * tell the callee that it can use the frame on its own. + */ +static int batadv_bla_process_claim(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + struct sk_buff *skb) +{ + struct ethhdr *ethhdr; + struct vlan_ethhdr *vhdr; + struct arphdr *arphdr; + uint8_t *hw_src, *hw_dst; + struct batadv_bla_claim_dst *bla_dst; + uint16_t proto; + int headlen; + short vid = -1; + int ret; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) { + vhdr = (struct vlan_ethhdr *)ethhdr; + vid = ntohs(vhdr->h_vlan_TCI) & VLAN_VID_MASK; + proto = ntohs(vhdr->h_vlan_encapsulated_proto); + headlen = sizeof(*vhdr); + } else { + proto = ntohs(ethhdr->h_proto); + headlen = ETH_HLEN; + } + + if (proto != ETH_P_ARP) + return 0; /* not a claim frame */ + + /* this must be a ARP frame. check if it is a claim. */ + + if (unlikely(!pskb_may_pull(skb, headlen + arp_hdr_len(skb->dev)))) + return 0; + + /* pskb_may_pull() may have modified the pointers, get ethhdr again */ + ethhdr = (struct ethhdr *)skb_mac_header(skb); + arphdr = (struct arphdr *)((uint8_t *)ethhdr + headlen); + + /* Check whether the ARP frame carries a valid + * IP information + */ + if (arphdr->ar_hrd != htons(ARPHRD_ETHER)) + return 0; + if (arphdr->ar_pro != htons(ETH_P_IP)) + return 0; + if (arphdr->ar_hln != ETH_ALEN) + return 0; + if (arphdr->ar_pln != 4) + return 0; + + hw_src = (uint8_t *)arphdr + sizeof(struct arphdr); + hw_dst = hw_src + ETH_ALEN + 4; + bla_dst = (struct batadv_bla_claim_dst *)hw_dst; + + /* check if it is a claim frame. */ + ret = batadv_check_claim_group(bat_priv, primary_if, hw_src, hw_dst, + ethhdr); + if (ret == 1) + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_process_claim(): received a claim frame from another group. From: %pM on vid %d ...(hw_src %pM, hw_dst %pM)\n", + ethhdr->h_source, vid, hw_src, hw_dst); + + if (ret < 2) + return ret; + + /* become a backbone gw ourselves on this vlan if not happened yet */ + batadv_bla_update_own_backbone_gw(bat_priv, primary_if, vid); + + /* check for the different types of claim frames ... */ + switch (bla_dst->type) { + case BATADV_CLAIM_TYPE_CLAIM: + if (batadv_handle_claim(bat_priv, primary_if, hw_src, + ethhdr->h_source, vid)) + return 1; + break; + case BATADV_CLAIM_TYPE_UNCLAIM: + if (batadv_handle_unclaim(bat_priv, primary_if, + ethhdr->h_source, hw_src, vid)) + return 1; + break; + + case BATADV_CLAIM_TYPE_ANNOUNCE: + if (batadv_handle_announce(bat_priv, hw_src, ethhdr->h_source, + vid)) + return 1; + break; + case BATADV_CLAIM_TYPE_REQUEST: + if (batadv_handle_request(bat_priv, primary_if, hw_src, ethhdr, + vid)) + return 1; + break; + } + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_process_claim(): ERROR - this looks like a claim frame, but is useless. eth src %pM on vid %d ...(hw_src %pM, hw_dst %pM)\n", + ethhdr->h_source, vid, hw_src, hw_dst); + return 1; +} + +/* Check when we last heard from other nodes, and remove them in case of + * a time out, or clean all backbone gws if now is set. + */ +static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now) +{ + struct batadv_bla_backbone_gw *backbone_gw; + struct hlist_node *node_tmp; + struct hlist_head *head; + struct batadv_hashtable *hash; + spinlock_t *list_lock; /* protects write access to the hash lists */ + int i; + + hash = bat_priv->bla.backbone_hash; + 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(backbone_gw, node_tmp, + head, hash_entry) { + if (now) + goto purge_now; + if (!batadv_has_timed_out(backbone_gw->lasttime, + BATADV_BLA_BACKBONE_TIMEOUT)) + continue; + + batadv_dbg(BATADV_DBG_BLA, backbone_gw->bat_priv, + "bla_purge_backbone_gw(): backbone gw %pM timed out\n", + backbone_gw->orig); + +purge_now: + /* don't wait for the pending request anymore */ + if (atomic_read(&backbone_gw->request_sent)) + atomic_dec(&bat_priv->bla.num_requests); + + batadv_bla_del_backbone_claims(backbone_gw); + + hlist_del_rcu(&backbone_gw->hash_entry); + batadv_backbone_gw_free_ref(backbone_gw); + } + spin_unlock_bh(list_lock); + } +} + +/** + * batadv_bla_purge_claims + * @bat_priv: the bat priv with all the soft interface information + * @primary_if: the selected primary interface, may be NULL if now is set + * @now: whether the whole hash shall be wiped now + * + * Check when we heard last time from our own claims, and remove them in case of + * a time out, or clean all claims if now is set + */ +static void batadv_bla_purge_claims(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + int now) +{ + struct batadv_bla_claim *claim; + struct hlist_head *head; + struct batadv_hashtable *hash; + int i; + + hash = bat_priv->bla.claim_hash; + if (!hash) + return; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(claim, head, hash_entry) { + if (now) + goto purge_now; + if (!batadv_compare_eth(claim->backbone_gw->orig, + primary_if->net_dev->dev_addr)) + continue; + if (!batadv_has_timed_out(claim->lasttime, + BATADV_BLA_CLAIM_TIMEOUT)) + continue; + + batadv_dbg(BATADV_DBG_BLA, bat_priv, + "bla_purge_claims(): %pM, vid %d, time out\n", + claim->addr, claim->vid); + +purge_now: + batadv_handle_unclaim(bat_priv, primary_if, + claim->backbone_gw->orig, + claim->addr, claim->vid); + } + rcu_read_unlock(); + } +} + +/** + * batadv_bla_update_orig_address + * @bat_priv: the bat priv with all the soft interface information + * @primary_if: the new selected primary_if + * @oldif: the old primary interface, may be NULL + * + * Update the backbone gateways when the own orig address changes. + */ +void batadv_bla_update_orig_address(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + struct batadv_hard_iface *oldif) +{ + struct batadv_bla_backbone_gw *backbone_gw; + struct hlist_head *head; + struct batadv_hashtable *hash; + __be16 group; + int i; + + /* reset bridge loop avoidance group id */ + group = htons(crc16(0, primary_if->net_dev->dev_addr, ETH_ALEN)); + bat_priv->bla.claim_dest.group = group; + + /* purge everything when bridge loop avoidance is turned off */ + if (!atomic_read(&bat_priv->bridge_loop_avoidance)) + oldif = NULL; + + if (!oldif) { + batadv_bla_purge_claims(bat_priv, NULL, 1); + batadv_bla_purge_backbone_gw(bat_priv, 1); + return; + } + + hash = bat_priv->bla.backbone_hash; + if (!hash) + return; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(backbone_gw, head, hash_entry) { + /* own orig still holds the old value. */ + if (!batadv_compare_eth(backbone_gw->orig, + oldif->net_dev->dev_addr)) + continue; + + memcpy(backbone_gw->orig, + primary_if->net_dev->dev_addr, ETH_ALEN); + /* send an announce frame so others will ask for our + * claims and update their tables. + */ + batadv_bla_send_announce(bat_priv, backbone_gw); + } + rcu_read_unlock(); + } +} + +/* periodic work to do: + * * purge structures when they are too old + * * send announcements + */ +static void batadv_bla_periodic_work(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct batadv_priv *bat_priv; + struct batadv_priv_bla *priv_bla; + struct hlist_head *head; + struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_hashtable *hash; + struct batadv_hard_iface *primary_if; + int i; + + delayed_work = container_of(work, struct delayed_work, work); + priv_bla = container_of(delayed_work, struct batadv_priv_bla, work); + bat_priv = container_of(priv_bla, struct batadv_priv, bla); + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + batadv_bla_purge_claims(bat_priv, primary_if, 0); + batadv_bla_purge_backbone_gw(bat_priv, 0); + + if (!atomic_read(&bat_priv->bridge_loop_avoidance)) + goto out; + + hash = bat_priv->bla.backbone_hash; + if (!hash) + goto out; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(backbone_gw, head, hash_entry) { + if (!batadv_compare_eth(backbone_gw->orig, + primary_if->net_dev->dev_addr)) + continue; + + backbone_gw->lasttime = jiffies; + + batadv_bla_send_announce(bat_priv, backbone_gw); + + /* request_sent is only set after creation to avoid + * problems when we are not yet known as backbone gw + * in the backbone. + * + * We can reset this now after we waited some periods + * to give bridge forward delays and bla group forming + * some grace time. + */ + + if (atomic_read(&backbone_gw->request_sent) == 0) + continue; + + if (!atomic_dec_and_test(&backbone_gw->wait_periods)) + continue; + + atomic_dec(&backbone_gw->bat_priv->bla.num_requests); + atomic_set(&backbone_gw->request_sent, 0); + } + rcu_read_unlock(); + } +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + + queue_delayed_work(batadv_event_workqueue, &bat_priv->bla.work, + msecs_to_jiffies(BATADV_BLA_PERIOD_LENGTH)); +} + +/* The hash for claim and backbone hash receive the same key because they + * are getting initialized by hash_new with the same key. Reinitializing + * them with to different keys to allow nested locking without generating + * lockdep warnings + */ +static struct lock_class_key batadv_claim_hash_lock_class_key; +static struct lock_class_key batadv_backbone_hash_lock_class_key; + +/* initialize all bla structures */ +int batadv_bla_init(struct batadv_priv *bat_priv) +{ + int i; + uint8_t claim_dest[ETH_ALEN] = {0xff, 0x43, 0x05, 0x00, 0x00, 0x00}; + struct batadv_hard_iface *primary_if; + uint16_t crc; + unsigned long entrytime; + + spin_lock_init(&bat_priv->bla.bcast_duplist_lock); + + batadv_dbg(BATADV_DBG_BLA, bat_priv, "bla hash registering\n"); + + /* setting claim destination address */ + memcpy(&bat_priv->bla.claim_dest.magic, claim_dest, 3); + bat_priv->bla.claim_dest.type = 0; + primary_if = batadv_primary_if_get_selected(bat_priv); + if (primary_if) { + crc = crc16(0, primary_if->net_dev->dev_addr, ETH_ALEN); + bat_priv->bla.claim_dest.group = htons(crc); + batadv_hardif_free_ref(primary_if); + } else { + bat_priv->bla.claim_dest.group = 0; /* will be set later */ + } + + /* initialize the duplicate list */ + entrytime = jiffies - msecs_to_jiffies(BATADV_DUPLIST_TIMEOUT); + for (i = 0; i < BATADV_DUPLIST_SIZE; i++) + bat_priv->bla.bcast_duplist[i].entrytime = entrytime; + bat_priv->bla.bcast_duplist_curr = 0; + + if (bat_priv->bla.claim_hash) + return 0; + + bat_priv->bla.claim_hash = batadv_hash_new(128); + bat_priv->bla.backbone_hash = batadv_hash_new(32); + + if (!bat_priv->bla.claim_hash || !bat_priv->bla.backbone_hash) + return -ENOMEM; + + batadv_hash_set_lock_class(bat_priv->bla.claim_hash, + &batadv_claim_hash_lock_class_key); + batadv_hash_set_lock_class(bat_priv->bla.backbone_hash, + &batadv_backbone_hash_lock_class_key); + + batadv_dbg(BATADV_DBG_BLA, bat_priv, "bla hashes initialized\n"); + + INIT_DELAYED_WORK(&bat_priv->bla.work, batadv_bla_periodic_work); + + queue_delayed_work(batadv_event_workqueue, &bat_priv->bla.work, + msecs_to_jiffies(BATADV_BLA_PERIOD_LENGTH)); + return 0; +} + +/** + * batadv_bla_check_bcast_duplist + * @bat_priv: the bat priv with all the soft interface information + * @skb: contains the bcast_packet to be checked + * + * check if it is on our broadcast list. Another gateway might + * have sent the same packet because it is connected to the same backbone, + * so we have to remove this duplicate. + * + * This is performed by checking the CRC, which will tell us + * with a good chance that it is the same packet. If it is furthermore + * sent by another host, drop it. We allow equal packets from + * the same host however as this might be intended. + */ +int batadv_bla_check_bcast_duplist(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + int i, curr, ret = 0; + __be32 crc; + struct batadv_bcast_packet *bcast_packet; + struct batadv_bcast_duplist_entry *entry; + + bcast_packet = (struct batadv_bcast_packet *)skb->data; + + /* calculate the crc ... */ + crc = batadv_skb_crc32(skb, (u8 *)(bcast_packet + 1)); + + spin_lock_bh(&bat_priv->bla.bcast_duplist_lock); + + for (i = 0; i < BATADV_DUPLIST_SIZE; i++) { + curr = (bat_priv->bla.bcast_duplist_curr + i); + curr %= BATADV_DUPLIST_SIZE; + entry = &bat_priv->bla.bcast_duplist[curr]; + + /* we can stop searching if the entry is too old ; + * later entries will be even older + */ + if (batadv_has_timed_out(entry->entrytime, + BATADV_DUPLIST_TIMEOUT)) + break; + + if (entry->crc != crc) + continue; + + if (batadv_compare_eth(entry->orig, bcast_packet->orig)) + continue; + + /* this entry seems to match: same crc, not too old, + * and from another gw. therefore return 1 to forbid it. + */ + ret = 1; + goto out; + } + /* not found, add a new entry (overwrite the oldest entry) + * and allow it, its the first occurence. + */ + curr = (bat_priv->bla.bcast_duplist_curr + BATADV_DUPLIST_SIZE - 1); + curr %= BATADV_DUPLIST_SIZE; + entry = &bat_priv->bla.bcast_duplist[curr]; + entry->crc = crc; + entry->entrytime = jiffies; + memcpy(entry->orig, bcast_packet->orig, ETH_ALEN); + bat_priv->bla.bcast_duplist_curr = curr; + +out: + spin_unlock_bh(&bat_priv->bla.bcast_duplist_lock); + + return ret; +} + + + +/* @bat_priv: the bat priv with all the soft interface information + * @orig: originator mac address + * + * check if the originator is a gateway for any VLAN ID. + * + * returns 1 if it is found, 0 otherwise + */ +int batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, uint8_t *orig) +{ + struct batadv_hashtable *hash = bat_priv->bla.backbone_hash; + struct hlist_head *head; + struct batadv_bla_backbone_gw *backbone_gw; + int i; + + if (!atomic_read(&bat_priv->bridge_loop_avoidance)) + return 0; + + if (!hash) + return 0; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(backbone_gw, head, hash_entry) { + if (batadv_compare_eth(backbone_gw->orig, orig)) { + rcu_read_unlock(); + return 1; + } + } + rcu_read_unlock(); + } + + return 0; +} + + +/** + * batadv_bla_is_backbone_gw + * @skb: the frame to be checked + * @orig_node: the orig_node of the frame + * @hdr_size: maximum length of the frame + * + * bla_is_backbone_gw inspects the skb for the VLAN ID and returns 1 + * if the orig_node is also a gateway on the soft interface, otherwise it + * returns 0. + */ +int batadv_bla_is_backbone_gw(struct sk_buff *skb, + struct batadv_orig_node *orig_node, int hdr_size) +{ + struct ethhdr *ethhdr; + struct vlan_ethhdr *vhdr; + struct batadv_bla_backbone_gw *backbone_gw; + short vid = -1; + + if (!atomic_read(&orig_node->bat_priv->bridge_loop_avoidance)) + return 0; + + /* first, find out the vid. */ + if (!pskb_may_pull(skb, hdr_size + ETH_HLEN)) + return 0; + + ethhdr = (struct ethhdr *)(((uint8_t *)skb->data) + hdr_size); + + if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) { + if (!pskb_may_pull(skb, hdr_size + sizeof(struct vlan_ethhdr))) + return 0; + + vhdr = (struct vlan_ethhdr *)(skb->data + hdr_size); + vid = ntohs(vhdr->h_vlan_TCI) & VLAN_VID_MASK; + } + + /* see if this originator is a backbone gw for this VLAN */ + backbone_gw = batadv_backbone_hash_find(orig_node->bat_priv, + orig_node->orig, vid); + if (!backbone_gw) + return 0; + + batadv_backbone_gw_free_ref(backbone_gw); + return 1; +} + +/* free all bla structures (for softinterface free or module unload) */ +void batadv_bla_free(struct batadv_priv *bat_priv) +{ + struct batadv_hard_iface *primary_if; + + cancel_delayed_work_sync(&bat_priv->bla.work); + primary_if = batadv_primary_if_get_selected(bat_priv); + + if (bat_priv->bla.claim_hash) { + batadv_bla_purge_claims(bat_priv, primary_if, 1); + batadv_hash_destroy(bat_priv->bla.claim_hash); + bat_priv->bla.claim_hash = NULL; + } + if (bat_priv->bla.backbone_hash) { + batadv_bla_purge_backbone_gw(bat_priv, 1); + batadv_hash_destroy(bat_priv->bla.backbone_hash); + bat_priv->bla.backbone_hash = NULL; + } + if (primary_if) + batadv_hardif_free_ref(primary_if); +} + +/** + * batadv_bla_rx + * @bat_priv: the bat priv with all the soft interface information + * @skb: the frame to be checked + * @vid: the VLAN ID of the frame + * @is_bcast: the packet came in a broadcast packet type. + * + * bla_rx avoidance checks if: + * * we have to race for a claim + * * if the frame is allowed on the LAN + * + * in these cases, the skb is further handled by this function and + * returns 1, otherwise it returns 0 and the caller shall further + * process the skb. + */ +int batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, short vid, + bool is_bcast) +{ + struct ethhdr *ethhdr; + struct batadv_bla_claim search_claim, *claim = NULL; + struct batadv_hard_iface *primary_if; + int ret; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto handled; + + if (!atomic_read(&bat_priv->bridge_loop_avoidance)) + goto allow; + + + if (unlikely(atomic_read(&bat_priv->bla.num_requests))) + /* don't allow broadcasts while requests are in flight */ + if (is_multicast_ether_addr(ethhdr->h_dest) && is_bcast) + goto handled; + + memcpy(search_claim.addr, ethhdr->h_source, ETH_ALEN); + search_claim.vid = vid; + claim = batadv_claim_hash_find(bat_priv, &search_claim); + + if (!claim) { + /* possible optimization: race for a claim */ + /* No claim exists yet, claim it for us! + */ + batadv_handle_claim(bat_priv, primary_if, + primary_if->net_dev->dev_addr, + ethhdr->h_source, vid); + goto allow; + } + + /* if it is our own claim ... */ + if (batadv_compare_eth(claim->backbone_gw->orig, + primary_if->net_dev->dev_addr)) { + /* ... allow it in any case */ + claim->lasttime = jiffies; + goto allow; + } + + /* if it is a broadcast ... */ + if (is_multicast_ether_addr(ethhdr->h_dest) && is_bcast) { + /* ... drop it. the responsible gateway is in charge. + * + * We need to check is_bcast because with the gateway + * feature, broadcasts (like DHCP requests) may be sent + * using a unicast packet type. + */ + goto handled; + } else { + /* seems the client considers us as its best gateway. + * send a claim and update the claim table + * immediately. + */ + batadv_handle_claim(bat_priv, primary_if, + primary_if->net_dev->dev_addr, + ethhdr->h_source, vid); + goto allow; + } +allow: + batadv_bla_update_own_backbone_gw(bat_priv, primary_if, vid); + ret = 0; + goto out; + +handled: + kfree_skb(skb); + ret = 1; + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (claim) + batadv_claim_free_ref(claim); + return ret; +} + +/** + * batadv_bla_tx + * @bat_priv: the bat priv with all the soft interface information + * @skb: the frame to be checked + * @vid: the VLAN ID of the frame + * + * bla_tx checks if: + * * a claim was received which has to be processed + * * the frame is allowed on the mesh + * + * in these cases, the skb is further handled by this function and + * returns 1, otherwise it returns 0 and the caller shall further + * process the skb. + */ +int batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb, short vid) +{ + struct ethhdr *ethhdr; + struct batadv_bla_claim search_claim, *claim = NULL; + struct batadv_hard_iface *primary_if; + int ret = 0; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + if (!atomic_read(&bat_priv->bridge_loop_avoidance)) + goto allow; + + /* in VLAN case, the mac header might not be set. */ + skb_reset_mac_header(skb); + + if (batadv_bla_process_claim(bat_priv, primary_if, skb)) + goto handled; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + if (unlikely(atomic_read(&bat_priv->bla.num_requests))) + /* don't allow broadcasts while requests are in flight */ + if (is_multicast_ether_addr(ethhdr->h_dest)) + goto handled; + + memcpy(search_claim.addr, ethhdr->h_source, ETH_ALEN); + search_claim.vid = vid; + + claim = batadv_claim_hash_find(bat_priv, &search_claim); + + /* if no claim exists, allow it. */ + if (!claim) + goto allow; + + /* check if we are responsible. */ + if (batadv_compare_eth(claim->backbone_gw->orig, + primary_if->net_dev->dev_addr)) { + /* if yes, the client has roamed and we have + * to unclaim it. + */ + batadv_handle_unclaim(bat_priv, primary_if, + primary_if->net_dev->dev_addr, + ethhdr->h_source, vid); + goto allow; + } + + /* check if it is a multicast/broadcast frame */ + if (is_multicast_ether_addr(ethhdr->h_dest)) { + /* drop it. the responsible gateway has forwarded it into + * the backbone network. + */ + goto handled; + } else { + /* we must allow it. at least if we are + * responsible for the DESTINATION. + */ + goto allow; + } +allow: + batadv_bla_update_own_backbone_gw(bat_priv, primary_if, vid); + ret = 0; + goto out; +handled: + ret = 1; +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (claim) + batadv_claim_free_ref(claim); + return ret; +} + +int batadv_bla_claim_table_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->bla.claim_hash; + struct batadv_bla_claim *claim; + struct batadv_hard_iface *primary_if; + struct hlist_head *head; + uint32_t i; + bool is_own; + uint8_t *primary_addr; + + primary_if = batadv_seq_print_text_primary_if_get(seq); + if (!primary_if) + goto out; + + primary_addr = primary_if->net_dev->dev_addr; + seq_printf(seq, + "Claims announced for the mesh %s (orig %pM, group id %#.4x)\n", + net_dev->name, primary_addr, + ntohs(bat_priv->bla.claim_dest.group)); + seq_printf(seq, " %-17s %-5s %-17s [o] (%-6s)\n", + "Client", "VID", "Originator", "CRC"); + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(claim, head, hash_entry) { + is_own = batadv_compare_eth(claim->backbone_gw->orig, + primary_addr); + seq_printf(seq, " * %pM on % 5d by %pM [%c] (%#.4x)\n", + claim->addr, claim->vid, + claim->backbone_gw->orig, + (is_own ? 'x' : ' '), + claim->backbone_gw->crc); + } + rcu_read_unlock(); + } +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return 0; +} + +int batadv_bla_backbone_table_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->bla.backbone_hash; + struct batadv_bla_backbone_gw *backbone_gw; + struct batadv_hard_iface *primary_if; + struct hlist_head *head; + int secs, msecs; + uint32_t i; + bool is_own; + uint8_t *primary_addr; + + primary_if = batadv_seq_print_text_primary_if_get(seq); + if (!primary_if) + goto out; + + primary_addr = primary_if->net_dev->dev_addr; + seq_printf(seq, + "Backbones announced for the mesh %s (orig %pM, group id %#.4x)\n", + net_dev->name, primary_addr, + ntohs(bat_priv->bla.claim_dest.group)); + seq_printf(seq, " %-17s %-5s %-9s (%-6s)\n", + "Originator", "VID", "last seen", "CRC"); + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(backbone_gw, head, hash_entry) { + msecs = jiffies_to_msecs(jiffies - + backbone_gw->lasttime); + secs = msecs / 1000; + msecs = msecs % 1000; + + is_own = batadv_compare_eth(backbone_gw->orig, + primary_addr); + if (is_own) + continue; + + seq_printf(seq, + " * %pM on % 5d % 4i.%03is (%#.4x)\n", + backbone_gw->orig, backbone_gw->vid, + secs, msecs, backbone_gw->crc); + } + rcu_read_unlock(); + } +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return 0; +} diff --git a/net/batman-adv/bridge_loop_avoidance.h b/net/batman-adv/bridge_loop_avoidance.h new file mode 100644 index 00000000..dea2fbc5 --- /dev/null +++ b/net/batman-adv/bridge_loop_avoidance.h @@ -0,0 +1,106 @@ +/* Copyright (C) 2011-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_BLA_H_ +#define _NET_BATMAN_ADV_BLA_H_ + +#ifdef CONFIG_BATMAN_ADV_BLA +int batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb, short vid, + bool is_bcast); +int batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb, short vid); +int batadv_bla_is_backbone_gw(struct sk_buff *skb, + struct batadv_orig_node *orig_node, int hdr_size); +int batadv_bla_claim_table_seq_print_text(struct seq_file *seq, void *offset); +int batadv_bla_backbone_table_seq_print_text(struct seq_file *seq, + void *offset); +int batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, uint8_t *orig); +int batadv_bla_check_bcast_duplist(struct batadv_priv *bat_priv, + struct sk_buff *skb); +void batadv_bla_update_orig_address(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + struct batadv_hard_iface *oldif); +int batadv_bla_init(struct batadv_priv *bat_priv); +void batadv_bla_free(struct batadv_priv *bat_priv); + +#define BATADV_BLA_CRC_INIT 0 +#else /* ifdef CONFIG_BATMAN_ADV_BLA */ + +static inline int batadv_bla_rx(struct batadv_priv *bat_priv, + struct sk_buff *skb, short vid, bool is_bcast) +{ + return 0; +} + +static inline int batadv_bla_tx(struct batadv_priv *bat_priv, + struct sk_buff *skb, short vid) +{ + return 0; +} + +static inline int batadv_bla_is_backbone_gw(struct sk_buff *skb, + struct batadv_orig_node *orig_node, + int hdr_size) +{ + return 0; +} + +static inline int batadv_bla_claim_table_seq_print_text(struct seq_file *seq, + void *offset) +{ + return 0; +} + +static inline int batadv_bla_backbone_table_seq_print_text(struct seq_file *seq, + void *offset) +{ + return 0; +} + +static inline int batadv_bla_is_backbone_gw_orig(struct batadv_priv *bat_priv, + uint8_t *orig) +{ + return 0; +} + +static inline int +batadv_bla_check_bcast_duplist(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + return 0; +} + +static inline void +batadv_bla_update_orig_address(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if, + struct batadv_hard_iface *oldif) +{ +} + +static inline int batadv_bla_init(struct batadv_priv *bat_priv) +{ + return 1; +} + +static inline void batadv_bla_free(struct batadv_priv *bat_priv) +{ +} + +#endif /* ifdef CONFIG_BATMAN_ADV_BLA */ + +#endif /* ifndef _NET_BATMAN_ADV_BLA_H_ */ diff --git a/net/batman-adv/debugfs.c b/net/batman-adv/debugfs.c new file mode 100644 index 00000000..f186a55b --- /dev/null +++ b/net/batman-adv/debugfs.c @@ -0,0 +1,474 @@ +/* Copyright (C) 2010-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 <linux/debugfs.h> + +#include "debugfs.h" +#include "translation-table.h" +#include "originator.h" +#include "hard-interface.h" +#include "gateway_common.h" +#include "gateway_client.h" +#include "soft-interface.h" +#include "vis.h" +#include "icmp_socket.h" +#include "bridge_loop_avoidance.h" +#include "distributed-arp-table.h" +#include "network-coding.h" + +static struct dentry *batadv_debugfs; + +#ifdef CONFIG_BATMAN_ADV_DEBUG +#define BATADV_LOG_BUFF_MASK (batadv_log_buff_len - 1) + +static const int batadv_log_buff_len = BATADV_LOG_BUF_LEN; + +static char *batadv_log_char_addr(struct batadv_priv_debug_log *debug_log, + size_t idx) +{ + return &debug_log->log_buff[idx & BATADV_LOG_BUFF_MASK]; +} + +static void batadv_emit_log_char(struct batadv_priv_debug_log *debug_log, + char c) +{ + char *char_addr; + + char_addr = batadv_log_char_addr(debug_log, debug_log->log_end); + *char_addr = c; + debug_log->log_end++; + + if (debug_log->log_end - debug_log->log_start > batadv_log_buff_len) + debug_log->log_start = debug_log->log_end - batadv_log_buff_len; +} + +__printf(2, 3) +static int batadv_fdebug_log(struct batadv_priv_debug_log *debug_log, + const char *fmt, ...) +{ + va_list args; + static char debug_log_buf[256]; + char *p; + + if (!debug_log) + return 0; + + spin_lock_bh(&debug_log->lock); + va_start(args, fmt); + vscnprintf(debug_log_buf, sizeof(debug_log_buf), fmt, args); + va_end(args); + + for (p = debug_log_buf; *p != 0; p++) + batadv_emit_log_char(debug_log, *p); + + spin_unlock_bh(&debug_log->lock); + + wake_up(&debug_log->queue_wait); + + return 0; +} + +int batadv_debug_log(struct batadv_priv *bat_priv, const char *fmt, ...) +{ + va_list args; + char tmp_log_buf[256]; + + va_start(args, fmt); + vscnprintf(tmp_log_buf, sizeof(tmp_log_buf), fmt, args); + batadv_fdebug_log(bat_priv->debug_log, "[%10u] %s", + jiffies_to_msecs(jiffies), tmp_log_buf); + va_end(args); + + return 0; +} + +static int batadv_log_open(struct inode *inode, struct file *file) +{ + if (!try_module_get(THIS_MODULE)) + return -EBUSY; + + nonseekable_open(inode, file); + file->private_data = inode->i_private; + return 0; +} + +static int batadv_log_release(struct inode *inode, struct file *file) +{ + module_put(THIS_MODULE); + return 0; +} + +static int batadv_log_empty(struct batadv_priv_debug_log *debug_log) +{ + return !(debug_log->log_start - debug_log->log_end); +} + +static ssize_t batadv_log_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct batadv_priv *bat_priv = file->private_data; + struct batadv_priv_debug_log *debug_log = bat_priv->debug_log; + int error, i = 0; + char *char_addr; + char c; + + if ((file->f_flags & O_NONBLOCK) && batadv_log_empty(debug_log)) + return -EAGAIN; + + if (!buf) + return -EINVAL; + + if (count == 0) + return 0; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + error = wait_event_interruptible(debug_log->queue_wait, + (!batadv_log_empty(debug_log))); + + if (error) + return error; + + spin_lock_bh(&debug_log->lock); + + while ((!error) && (i < count) && + (debug_log->log_start != debug_log->log_end)) { + char_addr = batadv_log_char_addr(debug_log, + debug_log->log_start); + c = *char_addr; + + debug_log->log_start++; + + spin_unlock_bh(&debug_log->lock); + + error = __put_user(c, buf); + + spin_lock_bh(&debug_log->lock); + + buf++; + i++; + } + + spin_unlock_bh(&debug_log->lock); + + if (!error) + return i; + + return error; +} + +static unsigned int batadv_log_poll(struct file *file, poll_table *wait) +{ + struct batadv_priv *bat_priv = file->private_data; + struct batadv_priv_debug_log *debug_log = bat_priv->debug_log; + + poll_wait(file, &debug_log->queue_wait, wait); + + if (!batadv_log_empty(debug_log)) + return POLLIN | POLLRDNORM; + + return 0; +} + +static const struct file_operations batadv_log_fops = { + .open = batadv_log_open, + .release = batadv_log_release, + .read = batadv_log_read, + .poll = batadv_log_poll, + .llseek = no_llseek, +}; + +static int batadv_debug_log_setup(struct batadv_priv *bat_priv) +{ + struct dentry *d; + + if (!bat_priv->debug_dir) + goto err; + + bat_priv->debug_log = kzalloc(sizeof(*bat_priv->debug_log), GFP_ATOMIC); + if (!bat_priv->debug_log) + goto err; + + spin_lock_init(&bat_priv->debug_log->lock); + init_waitqueue_head(&bat_priv->debug_log->queue_wait); + + d = debugfs_create_file("log", S_IFREG | S_IRUSR, + bat_priv->debug_dir, bat_priv, + &batadv_log_fops); + if (!d) + goto err; + + return 0; + +err: + return -ENOMEM; +} + +static void batadv_debug_log_cleanup(struct batadv_priv *bat_priv) +{ + kfree(bat_priv->debug_log); + bat_priv->debug_log = NULL; +} +#else /* CONFIG_BATMAN_ADV_DEBUG */ +static int batadv_debug_log_setup(struct batadv_priv *bat_priv) +{ + return 0; +} + +static void batadv_debug_log_cleanup(struct batadv_priv *bat_priv) +{ + return; +} +#endif + +static int batadv_algorithms_open(struct inode *inode, struct file *file) +{ + return single_open(file, batadv_algo_seq_print_text, NULL); +} + +static int batadv_originators_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_orig_seq_print_text, net_dev); +} + +static int batadv_gateways_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_gw_client_seq_print_text, net_dev); +} + +static int batadv_transtable_global_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_tt_global_seq_print_text, net_dev); +} + +#ifdef CONFIG_BATMAN_ADV_BLA +static int batadv_bla_claim_table_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_bla_claim_table_seq_print_text, + net_dev); +} + +static int batadv_bla_backbone_table_open(struct inode *inode, + struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_bla_backbone_table_seq_print_text, + net_dev); +} + +#endif + +#ifdef CONFIG_BATMAN_ADV_DAT +/** + * batadv_dat_cache_open - Prepare file handler for reads from dat_chache + * @inode: inode which was opened + * @file: file handle to be initialized + */ +static int batadv_dat_cache_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_dat_cache_seq_print_text, net_dev); +} +#endif + +static int batadv_transtable_local_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_tt_local_seq_print_text, net_dev); +} + +static int batadv_vis_data_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_vis_seq_print_text, net_dev); +} + +struct batadv_debuginfo { + struct attribute attr; + const struct file_operations fops; +}; + +#ifdef CONFIG_BATMAN_ADV_NC +static int batadv_nc_nodes_open(struct inode *inode, struct file *file) +{ + struct net_device *net_dev = (struct net_device *)inode->i_private; + return single_open(file, batadv_nc_nodes_seq_print_text, net_dev); +} +#endif + +#define BATADV_DEBUGINFO(_name, _mode, _open) \ +struct batadv_debuginfo batadv_debuginfo_##_name = { \ + .attr = { .name = __stringify(_name), \ + .mode = _mode, }, \ + .fops = { .owner = THIS_MODULE, \ + .open = _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ + } \ +}; + +/* the following attributes are general and therefore they will be directly + * placed in the BATADV_DEBUGFS_SUBDIR subdirectory of debugfs + */ +static BATADV_DEBUGINFO(routing_algos, S_IRUGO, batadv_algorithms_open); + +static struct batadv_debuginfo *batadv_general_debuginfos[] = { + &batadv_debuginfo_routing_algos, + NULL, +}; + +/* The following attributes are per soft interface */ +static BATADV_DEBUGINFO(originators, S_IRUGO, batadv_originators_open); +static BATADV_DEBUGINFO(gateways, S_IRUGO, batadv_gateways_open); +static BATADV_DEBUGINFO(transtable_global, S_IRUGO, + batadv_transtable_global_open); +#ifdef CONFIG_BATMAN_ADV_BLA +static BATADV_DEBUGINFO(bla_claim_table, S_IRUGO, batadv_bla_claim_table_open); +static BATADV_DEBUGINFO(bla_backbone_table, S_IRUGO, + batadv_bla_backbone_table_open); +#endif +#ifdef CONFIG_BATMAN_ADV_DAT +static BATADV_DEBUGINFO(dat_cache, S_IRUGO, batadv_dat_cache_open); +#endif +static BATADV_DEBUGINFO(transtable_local, S_IRUGO, + batadv_transtable_local_open); +static BATADV_DEBUGINFO(vis_data, S_IRUGO, batadv_vis_data_open); +#ifdef CONFIG_BATMAN_ADV_NC +static BATADV_DEBUGINFO(nc_nodes, S_IRUGO, batadv_nc_nodes_open); +#endif + +static struct batadv_debuginfo *batadv_mesh_debuginfos[] = { + &batadv_debuginfo_originators, + &batadv_debuginfo_gateways, + &batadv_debuginfo_transtable_global, +#ifdef CONFIG_BATMAN_ADV_BLA + &batadv_debuginfo_bla_claim_table, + &batadv_debuginfo_bla_backbone_table, +#endif +#ifdef CONFIG_BATMAN_ADV_DAT + &batadv_debuginfo_dat_cache, +#endif + &batadv_debuginfo_transtable_local, + &batadv_debuginfo_vis_data, +#ifdef CONFIG_BATMAN_ADV_NC + &batadv_debuginfo_nc_nodes, +#endif + NULL, +}; + +void batadv_debugfs_init(void) +{ + struct batadv_debuginfo **bat_debug; + struct dentry *file; + + batadv_debugfs = debugfs_create_dir(BATADV_DEBUGFS_SUBDIR, NULL); + if (batadv_debugfs == ERR_PTR(-ENODEV)) + batadv_debugfs = NULL; + + if (!batadv_debugfs) + goto err; + + for (bat_debug = batadv_general_debuginfos; *bat_debug; ++bat_debug) { + file = debugfs_create_file(((*bat_debug)->attr).name, + S_IFREG | ((*bat_debug)->attr).mode, + batadv_debugfs, NULL, + &(*bat_debug)->fops); + if (!file) { + pr_err("Can't add general debugfs file: %s\n", + ((*bat_debug)->attr).name); + goto err; + } + } + + return; +err: + debugfs_remove_recursive(batadv_debugfs); +} + +void batadv_debugfs_destroy(void) +{ + debugfs_remove_recursive(batadv_debugfs); + batadv_debugfs = NULL; +} + +int batadv_debugfs_add_meshif(struct net_device *dev) +{ + struct batadv_priv *bat_priv = netdev_priv(dev); + struct batadv_debuginfo **bat_debug; + struct dentry *file; + + if (!batadv_debugfs) + goto out; + + bat_priv->debug_dir = debugfs_create_dir(dev->name, batadv_debugfs); + if (!bat_priv->debug_dir) + goto out; + + if (batadv_socket_setup(bat_priv) < 0) + goto rem_attr; + + if (batadv_debug_log_setup(bat_priv) < 0) + goto rem_attr; + + for (bat_debug = batadv_mesh_debuginfos; *bat_debug; ++bat_debug) { + file = debugfs_create_file(((*bat_debug)->attr).name, + S_IFREG | ((*bat_debug)->attr).mode, + bat_priv->debug_dir, + dev, &(*bat_debug)->fops); + if (!file) { + batadv_err(dev, "Can't add debugfs file: %s/%s\n", + dev->name, ((*bat_debug)->attr).name); + goto rem_attr; + } + } + + if (batadv_nc_init_debugfs(bat_priv) < 0) + goto rem_attr; + + return 0; +rem_attr: + debugfs_remove_recursive(bat_priv->debug_dir); + bat_priv->debug_dir = NULL; +out: +#ifdef CONFIG_DEBUG_FS + return -ENOMEM; +#else + return 0; +#endif /* CONFIG_DEBUG_FS */ +} + +void batadv_debugfs_del_meshif(struct net_device *dev) +{ + struct batadv_priv *bat_priv = netdev_priv(dev); + + batadv_debug_log_cleanup(bat_priv); + + if (batadv_debugfs) { + debugfs_remove_recursive(bat_priv->debug_dir); + bat_priv->debug_dir = NULL; + } +} diff --git a/net/batman-adv/debugfs.h b/net/batman-adv/debugfs.h new file mode 100644 index 00000000..f8c3849e --- /dev/null +++ b/net/batman-adv/debugfs.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2010-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_DEBUGFS_H_ +#define _NET_BATMAN_ADV_DEBUGFS_H_ + +#define BATADV_DEBUGFS_SUBDIR "batman_adv" + +void batadv_debugfs_init(void); +void batadv_debugfs_destroy(void); +int batadv_debugfs_add_meshif(struct net_device *dev); +void batadv_debugfs_del_meshif(struct net_device *dev); + +#endif /* _NET_BATMAN_ADV_DEBUGFS_H_ */ diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c new file mode 100644 index 00000000..23999202 --- /dev/null +++ b/net/batman-adv/distributed-arp-table.c @@ -0,0 +1,1079 @@ +/* Copyright (C) 2011-2013 B.A.T.M.A.N. contributors: + * + * 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 <linux/if_ether.h> +#include <linux/if_arp.h> +#include <net/arp.h> + +#include "main.h" +#include "hash.h" +#include "distributed-arp-table.h" +#include "hard-interface.h" +#include "originator.h" +#include "send.h" +#include "types.h" +#include "translation-table.h" +#include "unicast.h" + +static void batadv_dat_purge(struct work_struct *work); + +/** + * batadv_dat_start_timer - initialise the DAT periodic worker + * @bat_priv: the bat priv with all the soft interface information + */ +static void batadv_dat_start_timer(struct batadv_priv *bat_priv) +{ + INIT_DELAYED_WORK(&bat_priv->dat.work, batadv_dat_purge); + queue_delayed_work(batadv_event_workqueue, &bat_priv->dat.work, + msecs_to_jiffies(10000)); +} + +/** + * batadv_dat_entry_free_ref - decrements the dat_entry refcounter and possibly + * free it + * @dat_entry: the oentry to free + */ +static void batadv_dat_entry_free_ref(struct batadv_dat_entry *dat_entry) +{ + if (atomic_dec_and_test(&dat_entry->refcount)) + kfree_rcu(dat_entry, rcu); +} + +/** + * batadv_dat_to_purge - checks whether a dat_entry has to be purged or not + * @dat_entry: the entry to check + * + * Returns true if the entry has to be purged now, false otherwise + */ +static bool batadv_dat_to_purge(struct batadv_dat_entry *dat_entry) +{ + return batadv_has_timed_out(dat_entry->last_update, + BATADV_DAT_ENTRY_TIMEOUT); +} + +/** + * __batadv_dat_purge - delete entries from the DAT local storage + * @bat_priv: the bat priv with all the soft interface information + * @to_purge: function in charge to decide whether an entry has to be purged or + * not. This function takes the dat_entry as argument and has to + * returns a boolean value: true is the entry has to be deleted, + * false otherwise + * + * Loops over each entry in the DAT local storage and delete it if and only if + * the to_purge function passed as argument returns true + */ +static void __batadv_dat_purge(struct batadv_priv *bat_priv, + bool (*to_purge)(struct batadv_dat_entry *)) +{ + spinlock_t *list_lock; /* protects write access to the hash lists */ + struct batadv_dat_entry *dat_entry; + struct hlist_node *node_tmp; + struct hlist_head *head; + uint32_t i; + + if (!bat_priv->dat.hash) + return; + + for (i = 0; i < bat_priv->dat.hash->size; i++) { + head = &bat_priv->dat.hash->table[i]; + list_lock = &bat_priv->dat.hash->list_locks[i]; + + spin_lock_bh(list_lock); + hlist_for_each_entry_safe(dat_entry, node_tmp, head, + hash_entry) { + /* if an helper function has been passed as parameter, + * ask it if the entry has to be purged or not + */ + if (to_purge && !to_purge(dat_entry)) + continue; + + hlist_del_rcu(&dat_entry->hash_entry); + batadv_dat_entry_free_ref(dat_entry); + } + spin_unlock_bh(list_lock); + } +} + +/** + * batadv_dat_purge - periodic task that deletes old entries from the local DAT + * hash table + * @work: kernel work struct + */ +static void batadv_dat_purge(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct batadv_priv_dat *priv_dat; + struct batadv_priv *bat_priv; + + delayed_work = container_of(work, struct delayed_work, work); + priv_dat = container_of(delayed_work, struct batadv_priv_dat, work); + bat_priv = container_of(priv_dat, struct batadv_priv, dat); + + __batadv_dat_purge(bat_priv, batadv_dat_to_purge); + batadv_dat_start_timer(bat_priv); +} + +/** + * batadv_compare_dat - comparing function used in the local DAT hash table + * @node: node in the local table + * @data2: second object to compare the node to + * + * Returns 1 if the two entry are the same, 0 otherwise + */ +static int batadv_compare_dat(const struct hlist_node *node, const void *data2) +{ + const void *data1 = container_of(node, struct batadv_dat_entry, + hash_entry); + + return (memcmp(data1, data2, sizeof(__be32)) == 0 ? 1 : 0); +} + +/** + * batadv_arp_hw_src - extract the hw_src field from an ARP packet + * @skb: ARP packet + * @hdr_size: size of the possible header before the ARP packet + * + * Returns the value of the hw_src field in the ARP packet + */ +static uint8_t *batadv_arp_hw_src(struct sk_buff *skb, int hdr_size) +{ + uint8_t *addr; + + addr = (uint8_t *)(skb->data + hdr_size); + addr += ETH_HLEN + sizeof(struct arphdr); + + return addr; +} + +/** + * batadv_arp_ip_src - extract the ip_src field from an ARP packet + * @skb: ARP packet + * @hdr_size: size of the possible header before the ARP packet + * + * Returns the value of the ip_src field in the ARP packet + */ +static __be32 batadv_arp_ip_src(struct sk_buff *skb, int hdr_size) +{ + return *(__be32 *)(batadv_arp_hw_src(skb, hdr_size) + ETH_ALEN); +} + +/** + * batadv_arp_hw_dst - extract the hw_dst field from an ARP packet + * @skb: ARP packet + * @hdr_size: size of the possible header before the ARP packet + * + * Returns the value of the hw_dst field in the ARP packet + */ +static uint8_t *batadv_arp_hw_dst(struct sk_buff *skb, int hdr_size) +{ + return batadv_arp_hw_src(skb, hdr_size) + ETH_ALEN + 4; +} + +/** + * batadv_arp_ip_dst - extract the ip_dst field from an ARP packet + * @skb: ARP packet + * @hdr_size: size of the possible header before the ARP packet + * + * Returns the value of the ip_dst field in the ARP packet + */ +static __be32 batadv_arp_ip_dst(struct sk_buff *skb, int hdr_size) +{ + return *(__be32 *)(batadv_arp_hw_src(skb, hdr_size) + ETH_ALEN * 2 + 4); +} + +/** + * batadv_hash_dat - compute the hash value for an IP address + * @data: data to hash + * @size: size of the hash table + * + * Returns the selected index in the hash table for the given data + */ +static uint32_t batadv_hash_dat(const void *data, uint32_t size) +{ + const unsigned char *key = data; + uint32_t hash = 0; + size_t i; + + for (i = 0; i < 4; i++) { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash % size; +} + +/** + * batadv_dat_entry_hash_find - looks for a given dat_entry in the local hash + * table + * @bat_priv: the bat priv with all the soft interface information + * @ip: search key + * + * Returns the dat_entry if found, NULL otherwise + */ +static struct batadv_dat_entry * +batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip) +{ + struct hlist_head *head; + struct batadv_dat_entry *dat_entry, *dat_entry_tmp = NULL; + struct batadv_hashtable *hash = bat_priv->dat.hash; + uint32_t index; + + if (!hash) + return NULL; + + index = batadv_hash_dat(&ip, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(dat_entry, head, hash_entry) { + if (dat_entry->ip != ip) + continue; + + if (!atomic_inc_not_zero(&dat_entry->refcount)) + continue; + + dat_entry_tmp = dat_entry; + break; + } + rcu_read_unlock(); + + return dat_entry_tmp; +} + +/** + * batadv_dat_entry_add - add a new dat entry or update it if already exists + * @bat_priv: the bat priv with all the soft interface information + * @ip: ipv4 to add/edit + * @mac_addr: mac address to assign to the given ipv4 + */ +static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip, + uint8_t *mac_addr) +{ + struct batadv_dat_entry *dat_entry; + int hash_added; + + dat_entry = batadv_dat_entry_hash_find(bat_priv, ip); + /* if this entry is already known, just update it */ + if (dat_entry) { + if (!batadv_compare_eth(dat_entry->mac_addr, mac_addr)) + memcpy(dat_entry->mac_addr, mac_addr, ETH_ALEN); + dat_entry->last_update = jiffies; + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "Entry updated: %pI4 %pM\n", &dat_entry->ip, + dat_entry->mac_addr); + goto out; + } + + dat_entry = kmalloc(sizeof(*dat_entry), GFP_ATOMIC); + if (!dat_entry) + goto out; + + dat_entry->ip = ip; + memcpy(dat_entry->mac_addr, mac_addr, ETH_ALEN); + dat_entry->last_update = jiffies; + atomic_set(&dat_entry->refcount, 2); + + hash_added = batadv_hash_add(bat_priv->dat.hash, batadv_compare_dat, + batadv_hash_dat, &dat_entry->ip, + &dat_entry->hash_entry); + + if (unlikely(hash_added != 0)) { + /* remove the reference for the hash */ + batadv_dat_entry_free_ref(dat_entry); + goto out; + } + + batadv_dbg(BATADV_DBG_DAT, bat_priv, "New entry added: %pI4 %pM\n", + &dat_entry->ip, dat_entry->mac_addr); + +out: + if (dat_entry) + batadv_dat_entry_free_ref(dat_entry); +} + +#ifdef CONFIG_BATMAN_ADV_DEBUG + +/** + * batadv_dbg_arp - print a debug message containing all the ARP packet details + * @bat_priv: the bat priv with all the soft interface information + * @skb: ARP packet + * @type: ARP type + * @hdr_size: size of the possible header before the ARP packet + * @msg: message to print together with the debugging information + */ +static void batadv_dbg_arp(struct batadv_priv *bat_priv, struct sk_buff *skb, + uint16_t type, int hdr_size, char *msg) +{ + struct batadv_unicast_4addr_packet *unicast_4addr_packet; + struct batadv_bcast_packet *bcast_pkt; + uint8_t *orig_addr; + __be32 ip_src, ip_dst; + + if (msg) + batadv_dbg(BATADV_DBG_DAT, bat_priv, "%s\n", msg); + + ip_src = batadv_arp_ip_src(skb, hdr_size); + ip_dst = batadv_arp_ip_dst(skb, hdr_size); + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "ARP MSG = [src: %pM-%pI4 dst: %pM-%pI4]\n", + batadv_arp_hw_src(skb, hdr_size), &ip_src, + batadv_arp_hw_dst(skb, hdr_size), &ip_dst); + + if (hdr_size == 0) + return; + + /* if the ARP packet is encapsulated in a batman packet, let's print + * some debug messages + */ + unicast_4addr_packet = (struct batadv_unicast_4addr_packet *)skb->data; + + switch (unicast_4addr_packet->u.header.packet_type) { + case BATADV_UNICAST: + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "* encapsulated within a UNICAST packet\n"); + break; + case BATADV_UNICAST_4ADDR: + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "* encapsulated within a UNICAST_4ADDR packet (src: %pM)\n", + unicast_4addr_packet->src); + switch (unicast_4addr_packet->subtype) { + case BATADV_P_DAT_DHT_PUT: + batadv_dbg(BATADV_DBG_DAT, bat_priv, "* type: DAT_DHT_PUT\n"); + break; + case BATADV_P_DAT_DHT_GET: + batadv_dbg(BATADV_DBG_DAT, bat_priv, "* type: DAT_DHT_GET\n"); + break; + case BATADV_P_DAT_CACHE_REPLY: + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "* type: DAT_CACHE_REPLY\n"); + break; + case BATADV_P_DATA: + batadv_dbg(BATADV_DBG_DAT, bat_priv, "* type: DATA\n"); + break; + default: + batadv_dbg(BATADV_DBG_DAT, bat_priv, "* type: Unknown (%u)!\n", + unicast_4addr_packet->u.header.packet_type); + } + break; + case BATADV_BCAST: + bcast_pkt = (struct batadv_bcast_packet *)unicast_4addr_packet; + orig_addr = bcast_pkt->orig; + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "* encapsulated within a BCAST packet (src: %pM)\n", + orig_addr); + break; + default: + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "* encapsulated within an unknown packet type (0x%x)\n", + unicast_4addr_packet->u.header.packet_type); + } +} + +#else + +static void batadv_dbg_arp(struct batadv_priv *bat_priv, struct sk_buff *skb, + uint16_t type, int hdr_size, char *msg) +{ +} + +#endif /* CONFIG_BATMAN_ADV_DEBUG */ + +/** + * batadv_is_orig_node_eligible - check whether a node can be a DHT candidate + * @res: the array with the already selected candidates + * @select: number of already selected candidates + * @tmp_max: address of the currently evaluated node + * @max: current round max address + * @last_max: address of the last selected candidate + * @candidate: orig_node under evaluation + * @max_orig_node: last selected candidate + * + * Returns true if the node has been elected as next candidate or false othrwise + */ +static bool batadv_is_orig_node_eligible(struct batadv_dat_candidate *res, + int select, batadv_dat_addr_t tmp_max, + batadv_dat_addr_t max, + batadv_dat_addr_t last_max, + struct batadv_orig_node *candidate, + struct batadv_orig_node *max_orig_node) +{ + bool ret = false; + int j; + + /* Check if this node has already been selected... */ + for (j = 0; j < select; j++) + if (res[j].orig_node == candidate) + break; + /* ..and possibly skip it */ + if (j < select) + goto out; + /* sanity check: has it already been selected? This should not happen */ + if (tmp_max > last_max) + goto out; + /* check if during this iteration an originator with a closer dht + * address has already been found + */ + if (tmp_max < max) + goto out; + /* this is an hash collision with the temporary selected node. Choose + * the one with the lowest address + */ + if ((tmp_max == max) && max_orig_node && + (batadv_compare_eth(candidate->orig, max_orig_node->orig) > 0)) + goto out; + + ret = true; +out: + return ret; +} + +/** + * batadv_choose_next_candidate - select the next DHT candidate + * @bat_priv: the bat priv with all the soft interface information + * @cands: candidates array + * @select: number of candidates already present in the array + * @ip_key: key to look up in the DHT + * @last_max: pointer where the address of the selected candidate will be saved + */ +static void batadv_choose_next_candidate(struct batadv_priv *bat_priv, + struct batadv_dat_candidate *cands, + int select, batadv_dat_addr_t ip_key, + batadv_dat_addr_t *last_max) +{ + batadv_dat_addr_t max = 0, tmp_max = 0; + struct batadv_orig_node *orig_node, *max_orig_node = NULL; + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + int i; + + /* if no node is eligible as candidate, leave the candidate type as + * NOT_FOUND + */ + cands[select].type = BATADV_DAT_CANDIDATE_NOT_FOUND; + + /* iterate over the originator list and find the node with closest + * dat_address which has not been selected yet + */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + /* the dht space is a ring and addresses are unsigned */ + tmp_max = BATADV_DAT_ADDR_MAX - orig_node->dat_addr + + ip_key; + + if (!batadv_is_orig_node_eligible(cands, select, + tmp_max, max, + *last_max, orig_node, + max_orig_node)) + continue; + + if (!atomic_inc_not_zero(&orig_node->refcount)) + continue; + + max = tmp_max; + if (max_orig_node) + batadv_orig_node_free_ref(max_orig_node); + max_orig_node = orig_node; + } + rcu_read_unlock(); + } + if (max_orig_node) { + cands[select].type = BATADV_DAT_CANDIDATE_ORIG; + cands[select].orig_node = max_orig_node; + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "dat_select_candidates() %d: selected %pM addr=%u dist=%u\n", + select, max_orig_node->orig, max_orig_node->dat_addr, + max); + } + *last_max = max; +} + +/** + * batadv_dat_select_candidates - selects the nodes which the DHT message has to + * be sent to + * @bat_priv: the bat priv with all the soft interface information + * @ip_dst: ipv4 to look up in the DHT + * + * An originator O is selected if and only if its DHT_ID value is one of three + * closest values (from the LEFT, with wrap around if needed) then the hash + * value of the key. ip_dst is the key. + * + * Returns the candidate array of size BATADV_DAT_CANDIDATE_NUM + */ +static struct batadv_dat_candidate * +batadv_dat_select_candidates(struct batadv_priv *bat_priv, __be32 ip_dst) +{ + int select; + batadv_dat_addr_t last_max = BATADV_DAT_ADDR_MAX, ip_key; + struct batadv_dat_candidate *res; + + if (!bat_priv->orig_hash) + return NULL; + + res = kmalloc(BATADV_DAT_CANDIDATES_NUM * sizeof(*res), GFP_ATOMIC); + if (!res) + return NULL; + + ip_key = (batadv_dat_addr_t)batadv_hash_dat(&ip_dst, + BATADV_DAT_ADDR_MAX); + + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "dat_select_candidates(): IP=%pI4 hash(IP)=%u\n", &ip_dst, + ip_key); + + for (select = 0; select < BATADV_DAT_CANDIDATES_NUM; select++) + batadv_choose_next_candidate(bat_priv, res, select, ip_key, + &last_max); + + return res; +} + +/** + * batadv_dat_send_data - send a payload to the selected candidates + * @bat_priv: the bat priv with all the soft interface information + * @skb: payload to send + * @ip: the DHT key + * @packet_subtype: unicast4addr packet subtype to use + * + * In this function the skb is copied by means of pskb_copy() and is sent as + * unicast packet to each of the selected candidates + * + * Returns true if the packet is sent to at least one candidate, false otherwise + */ +static bool batadv_dat_send_data(struct batadv_priv *bat_priv, + struct sk_buff *skb, __be32 ip, + int packet_subtype) +{ + int i; + bool ret = false; + int send_status; + struct batadv_neigh_node *neigh_node = NULL; + struct sk_buff *tmp_skb; + struct batadv_dat_candidate *cand; + + cand = batadv_dat_select_candidates(bat_priv, ip); + if (!cand) + goto out; + + batadv_dbg(BATADV_DBG_DAT, bat_priv, "DHT_SEND for %pI4\n", &ip); + + for (i = 0; i < BATADV_DAT_CANDIDATES_NUM; i++) { + if (cand[i].type == BATADV_DAT_CANDIDATE_NOT_FOUND) + continue; + + neigh_node = batadv_orig_node_get_router(cand[i].orig_node); + if (!neigh_node) + goto free_orig; + + tmp_skb = pskb_copy(skb, GFP_ATOMIC); + if (!batadv_unicast_4addr_prepare_skb(bat_priv, tmp_skb, + cand[i].orig_node, + packet_subtype)) { + kfree_skb(tmp_skb); + goto free_neigh; + } + + send_status = batadv_send_skb_packet(tmp_skb, + neigh_node->if_incoming, + neigh_node->addr); + if (send_status == NET_XMIT_SUCCESS) { + /* count the sent packet */ + switch (packet_subtype) { + case BATADV_P_DAT_DHT_GET: + batadv_inc_counter(bat_priv, + BATADV_CNT_DAT_GET_TX); + break; + case BATADV_P_DAT_DHT_PUT: + batadv_inc_counter(bat_priv, + BATADV_CNT_DAT_PUT_TX); + break; + } + + /* packet sent to a candidate: return true */ + ret = true; + } +free_neigh: + batadv_neigh_node_free_ref(neigh_node); +free_orig: + batadv_orig_node_free_ref(cand[i].orig_node); + } + +out: + kfree(cand); + return ret; +} + +/** + * batadv_dat_hash_free - free the local DAT hash table + * @bat_priv: the bat priv with all the soft interface information + */ +static void batadv_dat_hash_free(struct batadv_priv *bat_priv) +{ + if (!bat_priv->dat.hash) + return; + + __batadv_dat_purge(bat_priv, NULL); + + batadv_hash_destroy(bat_priv->dat.hash); + + bat_priv->dat.hash = NULL; +} + +/** + * batadv_dat_init - initialise the DAT internals + * @bat_priv: the bat priv with all the soft interface information + */ +int batadv_dat_init(struct batadv_priv *bat_priv) +{ + if (bat_priv->dat.hash) + return 0; + + bat_priv->dat.hash = batadv_hash_new(1024); + + if (!bat_priv->dat.hash) + return -ENOMEM; + + batadv_dat_start_timer(bat_priv); + + return 0; +} + +/** + * batadv_dat_free - free the DAT internals + * @bat_priv: the bat priv with all the soft interface information + */ +void batadv_dat_free(struct batadv_priv *bat_priv) +{ + cancel_delayed_work_sync(&bat_priv->dat.work); + + batadv_dat_hash_free(bat_priv); +} + +/** + * batadv_dat_cache_seq_print_text - print the local DAT hash table + * @seq: seq file to print on + * @offset: not used + */ +int batadv_dat_cache_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->dat.hash; + struct batadv_dat_entry *dat_entry; + struct batadv_hard_iface *primary_if; + struct hlist_head *head; + unsigned long last_seen_jiffies; + int last_seen_msecs, last_seen_secs, last_seen_mins; + uint32_t i; + + primary_if = batadv_seq_print_text_primary_if_get(seq); + if (!primary_if) + goto out; + + seq_printf(seq, "Distributed ARP Table (%s):\n", net_dev->name); + seq_printf(seq, " %-7s %-13s %5s\n", "IPv4", "MAC", + "last-seen"); + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(dat_entry, head, hash_entry) { + last_seen_jiffies = jiffies - dat_entry->last_update; + last_seen_msecs = jiffies_to_msecs(last_seen_jiffies); + last_seen_mins = last_seen_msecs / 60000; + last_seen_msecs = last_seen_msecs % 60000; + last_seen_secs = last_seen_msecs / 1000; + + seq_printf(seq, " * %15pI4 %14pM %6i:%02i\n", + &dat_entry->ip, dat_entry->mac_addr, + last_seen_mins, last_seen_secs); + } + rcu_read_unlock(); + } + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return 0; +} + +/** + * batadv_arp_get_type - parse an ARP packet and gets the type + * @bat_priv: the bat priv with all the soft interface information + * @skb: packet to analyse + * @hdr_size: size of the possible header before the ARP packet in the skb + * + * Returns the ARP type if the skb contains a valid ARP packet, 0 otherwise + */ +static uint16_t batadv_arp_get_type(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size) +{ + struct arphdr *arphdr; + struct ethhdr *ethhdr; + __be32 ip_src, ip_dst; + uint8_t *hw_src, *hw_dst; + uint16_t type = 0; + + /* pull the ethernet header */ + if (unlikely(!pskb_may_pull(skb, hdr_size + ETH_HLEN))) + goto out; + + ethhdr = (struct ethhdr *)(skb->data + hdr_size); + + if (ethhdr->h_proto != htons(ETH_P_ARP)) + goto out; + + /* pull the ARP payload */ + if (unlikely(!pskb_may_pull(skb, hdr_size + ETH_HLEN + + arp_hdr_len(skb->dev)))) + goto out; + + arphdr = (struct arphdr *)(skb->data + hdr_size + ETH_HLEN); + + /* Check whether the ARP packet carries a valid + * IP information + */ + if (arphdr->ar_hrd != htons(ARPHRD_ETHER)) + goto out; + + if (arphdr->ar_pro != htons(ETH_P_IP)) + goto out; + + if (arphdr->ar_hln != ETH_ALEN) + goto out; + + if (arphdr->ar_pln != 4) + goto out; + + /* Check for bad reply/request. If the ARP message is not sane, DAT + * will simply ignore it + */ + ip_src = batadv_arp_ip_src(skb, hdr_size); + ip_dst = batadv_arp_ip_dst(skb, hdr_size); + if (ipv4_is_loopback(ip_src) || ipv4_is_multicast(ip_src) || + ipv4_is_loopback(ip_dst) || ipv4_is_multicast(ip_dst) || + ipv4_is_zeronet(ip_src) || ipv4_is_lbcast(ip_src) || + ipv4_is_zeronet(ip_dst) || ipv4_is_lbcast(ip_dst)) + goto out; + + hw_src = batadv_arp_hw_src(skb, hdr_size); + if (is_zero_ether_addr(hw_src) || is_multicast_ether_addr(hw_src)) + goto out; + + /* we don't care about the destination MAC address in ARP requests */ + if (arphdr->ar_op != htons(ARPOP_REQUEST)) { + hw_dst = batadv_arp_hw_dst(skb, hdr_size); + if (is_zero_ether_addr(hw_dst) || + is_multicast_ether_addr(hw_dst)) + goto out; + } + + type = ntohs(arphdr->ar_op); +out: + return type; +} + +/** + * batadv_dat_snoop_outgoing_arp_request - snoop the ARP request and try to + * answer using DAT + * @bat_priv: the bat priv with all the soft interface information + * @skb: packet to check + * + * Returns true if the message has been sent to the dht candidates, false + * otherwise. In case of true the message has to be enqueued to permit the + * fallback + */ +bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + uint16_t type = 0; + __be32 ip_dst, ip_src; + uint8_t *hw_src; + bool ret = false; + struct batadv_dat_entry *dat_entry = NULL; + struct sk_buff *skb_new; + + if (!atomic_read(&bat_priv->distributed_arp_table)) + goto out; + + type = batadv_arp_get_type(bat_priv, skb, 0); + /* If the node gets an ARP_REQUEST it has to send a DHT_GET unicast + * message to the selected DHT candidates + */ + if (type != ARPOP_REQUEST) + goto out; + + batadv_dbg_arp(bat_priv, skb, type, 0, "Parsing outgoing ARP REQUEST"); + + ip_src = batadv_arp_ip_src(skb, 0); + hw_src = batadv_arp_hw_src(skb, 0); + ip_dst = batadv_arp_ip_dst(skb, 0); + + batadv_dat_entry_add(bat_priv, ip_src, hw_src); + + dat_entry = batadv_dat_entry_hash_find(bat_priv, ip_dst); + if (dat_entry) { + /* If the ARP request is destined for a local client the local + * client will answer itself. DAT would only generate a + * duplicate packet. + * + * Moreover, if the soft-interface is enslaved into a bridge, an + * additional DAT answer may trigger kernel warnings about + * a packet coming from the wrong port. + */ + if (batadv_is_my_client(bat_priv, dat_entry->mac_addr)) { + ret = true; + goto out; + } + + skb_new = arp_create(ARPOP_REPLY, ETH_P_ARP, ip_src, + bat_priv->soft_iface, ip_dst, hw_src, + dat_entry->mac_addr, hw_src); + if (!skb_new) + goto out; + + skb_reset_mac_header(skb_new); + skb_new->protocol = eth_type_trans(skb_new, + bat_priv->soft_iface); + bat_priv->stats.rx_packets++; + bat_priv->stats.rx_bytes += skb->len + ETH_HLEN; + bat_priv->soft_iface->last_rx = jiffies; + + netif_rx(skb_new); + batadv_dbg(BATADV_DBG_DAT, bat_priv, "ARP request replied locally\n"); + ret = true; + } else { + /* Send the request on the DHT */ + ret = batadv_dat_send_data(bat_priv, skb, ip_dst, + BATADV_P_DAT_DHT_GET); + } +out: + if (dat_entry) + batadv_dat_entry_free_ref(dat_entry); + return ret; +} + +/** + * batadv_dat_snoop_incoming_arp_request - snoop the ARP request and try to + * answer using the local DAT storage + * @bat_priv: the bat priv with all the soft interface information + * @skb: packet to check + * @hdr_size: size of the encapsulation header + * + * Returns true if the request has been answered, false otherwise + */ +bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size) +{ + uint16_t type; + __be32 ip_src, ip_dst; + uint8_t *hw_src; + struct sk_buff *skb_new; + struct batadv_dat_entry *dat_entry = NULL; + bool ret = false; + int err; + + if (!atomic_read(&bat_priv->distributed_arp_table)) + goto out; + + type = batadv_arp_get_type(bat_priv, skb, hdr_size); + if (type != ARPOP_REQUEST) + goto out; + + hw_src = batadv_arp_hw_src(skb, hdr_size); + ip_src = batadv_arp_ip_src(skb, hdr_size); + ip_dst = batadv_arp_ip_dst(skb, hdr_size); + + batadv_dbg_arp(bat_priv, skb, type, hdr_size, + "Parsing incoming ARP REQUEST"); + + batadv_dat_entry_add(bat_priv, ip_src, hw_src); + + dat_entry = batadv_dat_entry_hash_find(bat_priv, ip_dst); + if (!dat_entry) + goto out; + + skb_new = arp_create(ARPOP_REPLY, ETH_P_ARP, ip_src, + bat_priv->soft_iface, ip_dst, hw_src, + dat_entry->mac_addr, hw_src); + + if (!skb_new) + goto out; + + /* to preserve backwards compatibility, here the node has to answer + * using the same packet type it received for the request. This is due + * to that if a node is not using the 4addr packet format it may not + * support it. + */ + if (hdr_size == sizeof(struct batadv_unicast_4addr_packet)) + err = batadv_unicast_4addr_send_skb(bat_priv, skb_new, + BATADV_P_DAT_CACHE_REPLY); + else + err = batadv_unicast_send_skb(bat_priv, skb_new); + + if (!err) { + batadv_inc_counter(bat_priv, BATADV_CNT_DAT_CACHED_REPLY_TX); + ret = true; + } +out: + if (dat_entry) + batadv_dat_entry_free_ref(dat_entry); + if (ret) + kfree_skb(skb); + return ret; +} + +/** + * batadv_dat_snoop_outgoing_arp_reply - snoop the ARP reply and fill the DHT + * @bat_priv: the bat priv with all the soft interface information + * @skb: packet to check + */ +void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + uint16_t type; + __be32 ip_src, ip_dst; + uint8_t *hw_src, *hw_dst; + + if (!atomic_read(&bat_priv->distributed_arp_table)) + return; + + type = batadv_arp_get_type(bat_priv, skb, 0); + if (type != ARPOP_REPLY) + return; + + batadv_dbg_arp(bat_priv, skb, type, 0, "Parsing outgoing ARP REPLY"); + + hw_src = batadv_arp_hw_src(skb, 0); + ip_src = batadv_arp_ip_src(skb, 0); + hw_dst = batadv_arp_hw_dst(skb, 0); + ip_dst = batadv_arp_ip_dst(skb, 0); + + batadv_dat_entry_add(bat_priv, ip_src, hw_src); + batadv_dat_entry_add(bat_priv, ip_dst, hw_dst); + + /* Send the ARP reply to the candidates for both the IP addresses that + * the node got within the ARP reply + */ + batadv_dat_send_data(bat_priv, skb, ip_src, BATADV_P_DAT_DHT_PUT); + batadv_dat_send_data(bat_priv, skb, ip_dst, BATADV_P_DAT_DHT_PUT); +} +/** + * batadv_dat_snoop_incoming_arp_reply - snoop the ARP reply and fill the local + * DAT storage only + * @bat_priv: the bat priv with all the soft interface information + * @skb: packet to check + * @hdr_size: siaze of the encapsulation header + */ +bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size) +{ + uint16_t type; + __be32 ip_src, ip_dst; + uint8_t *hw_src, *hw_dst; + bool ret = false; + + if (!atomic_read(&bat_priv->distributed_arp_table)) + goto out; + + type = batadv_arp_get_type(bat_priv, skb, hdr_size); + if (type != ARPOP_REPLY) + goto out; + + batadv_dbg_arp(bat_priv, skb, type, hdr_size, + "Parsing incoming ARP REPLY"); + + hw_src = batadv_arp_hw_src(skb, hdr_size); + ip_src = batadv_arp_ip_src(skb, hdr_size); + hw_dst = batadv_arp_hw_dst(skb, hdr_size); + ip_dst = batadv_arp_ip_dst(skb, hdr_size); + + /* Update our internal cache with both the IP addresses the node got + * within the ARP reply + */ + batadv_dat_entry_add(bat_priv, ip_src, hw_src); + batadv_dat_entry_add(bat_priv, ip_dst, hw_dst); + + /* if this REPLY is directed to a client of mine, let's deliver the + * packet to the interface + */ + ret = !batadv_is_my_client(bat_priv, hw_dst); +out: + if (ret) + kfree_skb(skb); + /* if ret == false -> packet has to be delivered to the interface */ + return ret; +} + +/** + * batadv_dat_drop_broadcast_packet - check if an ARP request has to be dropped + * (because the node has already got the reply via DAT) or not + * @bat_priv: the bat priv with all the soft interface information + * @forw_packet: the broadcast packet + * + * Returns true if the node can drop the packet, false otherwise + */ +bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, + struct batadv_forw_packet *forw_packet) +{ + uint16_t type; + __be32 ip_dst; + struct batadv_dat_entry *dat_entry = NULL; + bool ret = false; + const size_t bcast_len = sizeof(struct batadv_bcast_packet); + + if (!atomic_read(&bat_priv->distributed_arp_table)) + goto out; + + /* If this packet is an ARP_REQUEST and the node already has the + * information that it is going to ask, then the packet can be dropped + */ + if (forw_packet->num_packets) + goto out; + + type = batadv_arp_get_type(bat_priv, forw_packet->skb, bcast_len); + if (type != ARPOP_REQUEST) + goto out; + + ip_dst = batadv_arp_ip_dst(forw_packet->skb, bcast_len); + dat_entry = batadv_dat_entry_hash_find(bat_priv, ip_dst); + /* check if the node already got this entry */ + if (!dat_entry) { + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "ARP Request for %pI4: fallback\n", &ip_dst); + goto out; + } + + batadv_dbg(BATADV_DBG_DAT, bat_priv, + "ARP Request for %pI4: fallback prevented\n", &ip_dst); + ret = true; + +out: + if (dat_entry) + batadv_dat_entry_free_ref(dat_entry); + return ret; +} diff --git a/net/batman-adv/distributed-arp-table.h b/net/batman-adv/distributed-arp-table.h new file mode 100644 index 00000000..125c8c6f --- /dev/null +++ b/net/batman-adv/distributed-arp-table.h @@ -0,0 +1,167 @@ +/* Copyright (C) 2011-2013 B.A.T.M.A.N. contributors: + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_ARP_H_ +#define _NET_BATMAN_ADV_ARP_H_ + +#ifdef CONFIG_BATMAN_ADV_DAT + +#include "types.h" +#include "originator.h" + +#include <linux/if_arp.h> + +#define BATADV_DAT_ADDR_MAX ((batadv_dat_addr_t)~(batadv_dat_addr_t)0) + +bool batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, + struct sk_buff *skb); +bool batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size); +void batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, + struct sk_buff *skb); +bool batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size); +bool batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, + struct batadv_forw_packet *forw_packet); + +/** + * batadv_dat_init_orig_node_addr - assign a DAT address to the orig_node + * @orig_node: the node to assign the DAT address to + */ +static inline void +batadv_dat_init_orig_node_addr(struct batadv_orig_node *orig_node) +{ + uint32_t addr; + + addr = batadv_choose_orig(orig_node->orig, BATADV_DAT_ADDR_MAX); + orig_node->dat_addr = (batadv_dat_addr_t)addr; +} + +/** + * batadv_dat_init_own_addr - assign a DAT address to the node itself + * @bat_priv: the bat priv with all the soft interface information + * @primary_if: a pointer to the primary interface + */ +static inline void +batadv_dat_init_own_addr(struct batadv_priv *bat_priv, + struct batadv_hard_iface *primary_if) +{ + uint32_t addr; + + addr = batadv_choose_orig(primary_if->net_dev->dev_addr, + BATADV_DAT_ADDR_MAX); + + bat_priv->dat.addr = (batadv_dat_addr_t)addr; +} + +int batadv_dat_init(struct batadv_priv *bat_priv); +void batadv_dat_free(struct batadv_priv *bat_priv); +int batadv_dat_cache_seq_print_text(struct seq_file *seq, void *offset); + +/** + * batadv_dat_inc_counter - increment the correct DAT packet counter + * @bat_priv: the bat priv with all the soft interface information + * @subtype: the 4addr subtype of the packet to be counted + * + * Updates the ethtool statistics for the received packet if it is a DAT subtype + */ +static inline void batadv_dat_inc_counter(struct batadv_priv *bat_priv, + uint8_t subtype) +{ + switch (subtype) { + case BATADV_P_DAT_DHT_GET: + batadv_inc_counter(bat_priv, + BATADV_CNT_DAT_GET_RX); + break; + case BATADV_P_DAT_DHT_PUT: + batadv_inc_counter(bat_priv, + BATADV_CNT_DAT_PUT_RX); + break; + } +} + +#else + +static inline bool +batadv_dat_snoop_outgoing_arp_request(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + return false; +} + +static inline bool +batadv_dat_snoop_incoming_arp_request(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size) +{ + return false; +} + +static inline bool +batadv_dat_snoop_outgoing_arp_reply(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + return false; +} + +static inline bool +batadv_dat_snoop_incoming_arp_reply(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size) +{ + return false; +} + +static inline bool +batadv_dat_drop_broadcast_packet(struct batadv_priv *bat_priv, + struct batadv_forw_packet *forw_packet) +{ + return false; +} + +static inline void +batadv_dat_init_orig_node_addr(struct batadv_orig_node *orig_node) +{ +} + +static inline void batadv_dat_init_own_addr(struct batadv_priv *bat_priv, + struct batadv_hard_iface *iface) +{ +} + +static inline void batadv_arp_change_timeout(struct net_device *soft_iface, + const char *name) +{ +} + +static inline int batadv_dat_init(struct batadv_priv *bat_priv) +{ + return 0; +} + +static inline void batadv_dat_free(struct batadv_priv *bat_priv) +{ +} + +static inline void batadv_dat_inc_counter(struct batadv_priv *bat_priv, + uint8_t subtype) +{ +} + +#endif /* CONFIG_BATMAN_ADV_DAT */ + +#endif /* _NET_BATMAN_ADV_ARP_H_ */ diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c new file mode 100644 index 00000000..f105219f --- /dev/null +++ b/net/batman-adv/gateway_client.c @@ -0,0 +1,712 @@ +/* Copyright (C) 2009-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 "sysfs.h" +#include "gateway_client.h" +#include "gateway_common.h" +#include "hard-interface.h" +#include "originator.h" +#include "translation-table.h" +#include "routing.h" +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <linux/udp.h> +#include <linux/if_vlan.h> + +/* This is the offset of the options field in a dhcp packet starting at + * the beginning of the dhcp header + */ +#define BATADV_DHCP_OPTIONS_OFFSET 240 +#define BATADV_DHCP_REQUEST 3 + +static void batadv_gw_node_free_ref(struct batadv_gw_node *gw_node) +{ + if (atomic_dec_and_test(&gw_node->refcount)) + kfree_rcu(gw_node, rcu); +} + +static struct batadv_gw_node * +batadv_gw_get_selected_gw_node(struct batadv_priv *bat_priv) +{ + struct batadv_gw_node *gw_node; + + rcu_read_lock(); + gw_node = rcu_dereference(bat_priv->gw.curr_gw); + if (!gw_node) + goto out; + + if (!atomic_inc_not_zero(&gw_node->refcount)) + gw_node = NULL; + +out: + rcu_read_unlock(); + return gw_node; +} + +struct batadv_orig_node * +batadv_gw_get_selected_orig(struct batadv_priv *bat_priv) +{ + struct batadv_gw_node *gw_node; + struct batadv_orig_node *orig_node = NULL; + + gw_node = batadv_gw_get_selected_gw_node(bat_priv); + if (!gw_node) + goto out; + + rcu_read_lock(); + orig_node = gw_node->orig_node; + if (!orig_node) + goto unlock; + + if (!atomic_inc_not_zero(&orig_node->refcount)) + orig_node = NULL; + +unlock: + rcu_read_unlock(); +out: + if (gw_node) + batadv_gw_node_free_ref(gw_node); + return orig_node; +} + +static void batadv_gw_select(struct batadv_priv *bat_priv, + struct batadv_gw_node *new_gw_node) +{ + struct batadv_gw_node *curr_gw_node; + + spin_lock_bh(&bat_priv->gw.list_lock); + + if (new_gw_node && !atomic_inc_not_zero(&new_gw_node->refcount)) + new_gw_node = NULL; + + curr_gw_node = rcu_dereference_protected(bat_priv->gw.curr_gw, 1); + rcu_assign_pointer(bat_priv->gw.curr_gw, new_gw_node); + + if (curr_gw_node) + batadv_gw_node_free_ref(curr_gw_node); + + spin_unlock_bh(&bat_priv->gw.list_lock); +} + +void batadv_gw_deselect(struct batadv_priv *bat_priv) +{ + atomic_set(&bat_priv->gw.reselect, 1); +} + +static struct batadv_gw_node * +batadv_gw_get_best_gw_node(struct batadv_priv *bat_priv) +{ + struct batadv_neigh_node *router; + struct batadv_gw_node *gw_node, *curr_gw = NULL; + uint32_t max_gw_factor = 0, tmp_gw_factor = 0; + uint32_t gw_divisor; + uint8_t max_tq = 0; + int down, up; + uint8_t tq_avg; + struct batadv_orig_node *orig_node; + + gw_divisor = BATADV_TQ_LOCAL_WINDOW_SIZE * BATADV_TQ_LOCAL_WINDOW_SIZE; + gw_divisor *= 64; + + rcu_read_lock(); + hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.list, list) { + if (gw_node->deleted) + continue; + + orig_node = gw_node->orig_node; + router = batadv_orig_node_get_router(orig_node); + if (!router) + continue; + + if (!atomic_inc_not_zero(&gw_node->refcount)) + goto next; + + tq_avg = router->tq_avg; + + switch (atomic_read(&bat_priv->gw_sel_class)) { + case 1: /* fast connection */ + batadv_gw_bandwidth_to_kbit(orig_node->gw_flags, + &down, &up); + + tmp_gw_factor = tq_avg * tq_avg * down * 100 * 100; + tmp_gw_factor /= gw_divisor; + + if ((tmp_gw_factor > max_gw_factor) || + ((tmp_gw_factor == max_gw_factor) && + (tq_avg > max_tq))) { + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); + curr_gw = gw_node; + atomic_inc(&curr_gw->refcount); + } + break; + + default: /* 2: stable connection (use best statistic) + * 3: fast-switch (use best statistic but change as + * soon as a better gateway appears) + * XX: late-switch (use best statistic but change as + * soon as a better gateway appears which has + * $routing_class more tq points) + */ + if (tq_avg > max_tq) { + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); + curr_gw = gw_node; + atomic_inc(&curr_gw->refcount); + } + break; + } + + if (tq_avg > max_tq) + max_tq = tq_avg; + + if (tmp_gw_factor > max_gw_factor) + max_gw_factor = tmp_gw_factor; + + batadv_gw_node_free_ref(gw_node); + +next: + batadv_neigh_node_free_ref(router); + } + rcu_read_unlock(); + + return curr_gw; +} + +void batadv_gw_election(struct batadv_priv *bat_priv) +{ + struct batadv_gw_node *curr_gw = NULL, *next_gw = NULL; + struct batadv_neigh_node *router = NULL; + char gw_addr[18] = { '\0' }; + + /* The batman daemon checks here if we already passed a full originator + * cycle in order to make sure we don't choose the first gateway we + * hear about. This check is based on the daemon's uptime which we + * don't have. + */ + if (atomic_read(&bat_priv->gw_mode) != BATADV_GW_MODE_CLIENT) + goto out; + + curr_gw = batadv_gw_get_selected_gw_node(bat_priv); + + if (!batadv_atomic_dec_not_zero(&bat_priv->gw.reselect) && curr_gw) + goto out; + + next_gw = batadv_gw_get_best_gw_node(bat_priv); + + if (curr_gw == next_gw) + goto out; + + if (next_gw) { + sprintf(gw_addr, "%pM", next_gw->orig_node->orig); + + router = batadv_orig_node_get_router(next_gw->orig_node); + if (!router) { + batadv_gw_deselect(bat_priv); + goto out; + } + } + + if ((curr_gw) && (!next_gw)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Removing selected gateway - no gateway in range\n"); + batadv_throw_uevent(bat_priv, BATADV_UEV_GW, BATADV_UEV_DEL, + NULL); + } else if ((!curr_gw) && (next_gw)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Adding route to gateway %pM (gw_flags: %i, tq: %i)\n", + next_gw->orig_node->orig, + next_gw->orig_node->gw_flags, router->tq_avg); + batadv_throw_uevent(bat_priv, BATADV_UEV_GW, BATADV_UEV_ADD, + gw_addr); + } else { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Changing route to gateway %pM (gw_flags: %i, tq: %i)\n", + next_gw->orig_node->orig, + next_gw->orig_node->gw_flags, router->tq_avg); + batadv_throw_uevent(bat_priv, BATADV_UEV_GW, BATADV_UEV_CHANGE, + gw_addr); + } + + batadv_gw_select(bat_priv, next_gw); + +out: + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); + if (next_gw) + batadv_gw_node_free_ref(next_gw); + if (router) + batadv_neigh_node_free_ref(router); +} + +void batadv_gw_check_election(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node) +{ + struct batadv_orig_node *curr_gw_orig; + struct batadv_neigh_node *router_gw = NULL, *router_orig = NULL; + uint8_t gw_tq_avg, orig_tq_avg; + + curr_gw_orig = batadv_gw_get_selected_orig(bat_priv); + if (!curr_gw_orig) + goto deselect; + + router_gw = batadv_orig_node_get_router(curr_gw_orig); + if (!router_gw) + goto deselect; + + /* this node already is the gateway */ + if (curr_gw_orig == orig_node) + goto out; + + router_orig = batadv_orig_node_get_router(orig_node); + if (!router_orig) + goto out; + + gw_tq_avg = router_gw->tq_avg; + orig_tq_avg = router_orig->tq_avg; + + /* the TQ value has to be better */ + if (orig_tq_avg < gw_tq_avg) + goto out; + + /* if the routing class is greater than 3 the value tells us how much + * greater the TQ value of the new gateway must be + */ + if ((atomic_read(&bat_priv->gw_sel_class) > 3) && + (orig_tq_avg - gw_tq_avg < atomic_read(&bat_priv->gw_sel_class))) + goto out; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Restarting gateway selection: better gateway found (tq curr: %i, tq new: %i)\n", + gw_tq_avg, orig_tq_avg); + +deselect: + batadv_gw_deselect(bat_priv); +out: + if (curr_gw_orig) + batadv_orig_node_free_ref(curr_gw_orig); + if (router_gw) + batadv_neigh_node_free_ref(router_gw); + if (router_orig) + batadv_neigh_node_free_ref(router_orig); + + return; +} + +static void batadv_gw_node_add(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + uint8_t new_gwflags) +{ + struct batadv_gw_node *gw_node; + int down, up; + + gw_node = kzalloc(sizeof(*gw_node), GFP_ATOMIC); + if (!gw_node) + return; + + INIT_HLIST_NODE(&gw_node->list); + gw_node->orig_node = orig_node; + atomic_set(&gw_node->refcount, 1); + + spin_lock_bh(&bat_priv->gw.list_lock); + hlist_add_head_rcu(&gw_node->list, &bat_priv->gw.list); + spin_unlock_bh(&bat_priv->gw.list_lock); + + batadv_gw_bandwidth_to_kbit(new_gwflags, &down, &up); + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Found new gateway %pM -> gw_class: %i - %i%s/%i%s\n", + orig_node->orig, new_gwflags, + (down > 2048 ? down / 1024 : down), + (down > 2048 ? "MBit" : "KBit"), + (up > 2048 ? up / 1024 : up), + (up > 2048 ? "MBit" : "KBit")); +} + +void batadv_gw_node_update(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + uint8_t new_gwflags) +{ + struct batadv_gw_node *gw_node, *curr_gw; + + /* Note: We don't need a NULL check here, since curr_gw never gets + * dereferenced. If curr_gw is NULL we also should not exit as we may + * have this gateway in our list (duplication check!) even though we + * have no currently selected gateway. + */ + curr_gw = batadv_gw_get_selected_gw_node(bat_priv); + + rcu_read_lock(); + hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.list, list) { + if (gw_node->orig_node != orig_node) + continue; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Gateway class of originator %pM changed from %i to %i\n", + orig_node->orig, gw_node->orig_node->gw_flags, + new_gwflags); + + gw_node->deleted = 0; + + if (new_gwflags == BATADV_NO_FLAGS) { + gw_node->deleted = jiffies; + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Gateway %pM removed from gateway list\n", + orig_node->orig); + + if (gw_node == curr_gw) + goto deselect; + } + + goto unlock; + } + + if (new_gwflags == BATADV_NO_FLAGS) + goto unlock; + + batadv_gw_node_add(bat_priv, orig_node, new_gwflags); + goto unlock; + +deselect: + batadv_gw_deselect(bat_priv); +unlock: + rcu_read_unlock(); + + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); +} + +void batadv_gw_node_delete(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node) +{ + batadv_gw_node_update(bat_priv, orig_node, 0); +} + +void batadv_gw_node_purge(struct batadv_priv *bat_priv) +{ + struct batadv_gw_node *gw_node, *curr_gw; + struct hlist_node *node_tmp; + unsigned long timeout = msecs_to_jiffies(2 * BATADV_PURGE_TIMEOUT); + int do_deselect = 0; + + curr_gw = batadv_gw_get_selected_gw_node(bat_priv); + + spin_lock_bh(&bat_priv->gw.list_lock); + + hlist_for_each_entry_safe(gw_node, node_tmp, + &bat_priv->gw.list, list) { + if (((!gw_node->deleted) || + (time_before(jiffies, gw_node->deleted + timeout))) && + atomic_read(&bat_priv->mesh_state) == BATADV_MESH_ACTIVE) + continue; + + if (curr_gw == gw_node) + do_deselect = 1; + + hlist_del_rcu(&gw_node->list); + batadv_gw_node_free_ref(gw_node); + } + + spin_unlock_bh(&bat_priv->gw.list_lock); + + /* gw_deselect() needs to acquire the gw_list_lock */ + if (do_deselect) + batadv_gw_deselect(bat_priv); + + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); +} + +/* fails if orig_node has no router */ +static int batadv_write_buffer_text(struct batadv_priv *bat_priv, + struct seq_file *seq, + const struct batadv_gw_node *gw_node) +{ + struct batadv_gw_node *curr_gw; + struct batadv_neigh_node *router; + int down, up, ret = -1; + + batadv_gw_bandwidth_to_kbit(gw_node->orig_node->gw_flags, &down, &up); + + router = batadv_orig_node_get_router(gw_node->orig_node); + if (!router) + goto out; + + curr_gw = batadv_gw_get_selected_gw_node(bat_priv); + + ret = seq_printf(seq, "%s %pM (%3i) %pM [%10s]: %3i - %i%s/%i%s\n", + (curr_gw == gw_node ? "=>" : " "), + gw_node->orig_node->orig, + router->tq_avg, router->addr, + router->if_incoming->net_dev->name, + gw_node->orig_node->gw_flags, + (down > 2048 ? down / 1024 : down), + (down > 2048 ? "MBit" : "KBit"), + (up > 2048 ? up / 1024 : up), + (up > 2048 ? "MBit" : "KBit")); + + batadv_neigh_node_free_ref(router); + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); +out: + return ret; +} + +int batadv_gw_client_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_hard_iface *primary_if; + struct batadv_gw_node *gw_node; + int gw_count = 0; + + primary_if = batadv_seq_print_text_primary_if_get(seq); + if (!primary_if) + goto out; + + seq_printf(seq, + " %-12s (%s/%i) %17s [%10s]: gw_class ... [B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%pM (%s)]\n", + "Gateway", "#", BATADV_TQ_MAX_VALUE, "Nexthop", "outgoingIF", + BATADV_SOURCE_VERSION, primary_if->net_dev->name, + primary_if->net_dev->dev_addr, net_dev->name); + + rcu_read_lock(); + hlist_for_each_entry_rcu(gw_node, &bat_priv->gw.list, list) { + if (gw_node->deleted) + continue; + + /* fails if orig_node has no router */ + if (batadv_write_buffer_text(bat_priv, seq, gw_node) < 0) + continue; + + gw_count++; + } + rcu_read_unlock(); + + if (gw_count == 0) + seq_puts(seq, "No gateways in range ...\n"); + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return 0; +} + +static bool batadv_is_type_dhcprequest(struct sk_buff *skb, int header_len) +{ + int ret = false; + unsigned char *p; + int pkt_len; + + if (skb_linearize(skb) < 0) + goto out; + + pkt_len = skb_headlen(skb); + + if (pkt_len < header_len + BATADV_DHCP_OPTIONS_OFFSET + 1) + goto out; + + p = skb->data + header_len + BATADV_DHCP_OPTIONS_OFFSET; + pkt_len -= header_len + BATADV_DHCP_OPTIONS_OFFSET + 1; + + /* Access the dhcp option lists. Each entry is made up by: + * - octet 1: option type + * - octet 2: option data len (only if type != 255 and 0) + * - octet 3: option data + */ + while (*p != 255 && !ret) { + /* p now points to the first octet: option type */ + if (*p == 53) { + /* type 53 is the message type option. + * Jump the len octet and go to the data octet + */ + if (pkt_len < 2) + goto out; + p += 2; + + /* check if the message type is what we need */ + if (*p == BATADV_DHCP_REQUEST) + ret = true; + break; + } else if (*p == 0) { + /* option type 0 (padding), just go forward */ + if (pkt_len < 1) + goto out; + pkt_len--; + p++; + } else { + /* This is any other option. So we get the length... */ + if (pkt_len < 1) + goto out; + pkt_len--; + p++; + + /* ...and then we jump over the data */ + if (pkt_len < 1 + (*p)) + goto out; + pkt_len -= 1 + (*p); + p += 1 + (*p); + } + } +out: + return ret; +} + +bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len) +{ + struct ethhdr *ethhdr; + struct iphdr *iphdr; + struct ipv6hdr *ipv6hdr; + struct udphdr *udphdr; + + /* check for ethernet header */ + if (!pskb_may_pull(skb, *header_len + ETH_HLEN)) + return false; + ethhdr = (struct ethhdr *)skb->data; + *header_len += ETH_HLEN; + + /* check for initial vlan header */ + if (ntohs(ethhdr->h_proto) == ETH_P_8021Q) { + if (!pskb_may_pull(skb, *header_len + VLAN_HLEN)) + return false; + ethhdr = (struct ethhdr *)(skb->data + VLAN_HLEN); + *header_len += VLAN_HLEN; + } + + /* check for ip header */ + switch (ntohs(ethhdr->h_proto)) { + case ETH_P_IP: + if (!pskb_may_pull(skb, *header_len + sizeof(*iphdr))) + return false; + iphdr = (struct iphdr *)(skb->data + *header_len); + *header_len += iphdr->ihl * 4; + + /* check for udp header */ + if (iphdr->protocol != IPPROTO_UDP) + return false; + + break; + case ETH_P_IPV6: + if (!pskb_may_pull(skb, *header_len + sizeof(*ipv6hdr))) + return false; + ipv6hdr = (struct ipv6hdr *)(skb->data + *header_len); + *header_len += sizeof(*ipv6hdr); + + /* check for udp header */ + if (ipv6hdr->nexthdr != IPPROTO_UDP) + return false; + + break; + default: + return false; + } + + if (!pskb_may_pull(skb, *header_len + sizeof(*udphdr))) + return false; + udphdr = (struct udphdr *)(skb->data + *header_len); + *header_len += sizeof(*udphdr); + + /* check for bootp port */ + if ((ntohs(ethhdr->h_proto) == ETH_P_IP) && + (ntohs(udphdr->dest) != 67)) + return false; + + if ((ntohs(ethhdr->h_proto) == ETH_P_IPV6) && + (ntohs(udphdr->dest) != 547)) + return false; + + return true; +} + +bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, + struct sk_buff *skb, struct ethhdr *ethhdr) +{ + struct batadv_neigh_node *neigh_curr = NULL, *neigh_old = NULL; + struct batadv_orig_node *orig_dst_node = NULL; + struct batadv_gw_node *curr_gw = NULL; + bool ret, out_of_range = false; + unsigned int header_len = 0; + uint8_t curr_tq_avg; + + ret = batadv_gw_is_dhcp_target(skb, &header_len); + if (!ret) + goto out; + + orig_dst_node = batadv_transtable_search(bat_priv, ethhdr->h_source, + ethhdr->h_dest); + if (!orig_dst_node) + goto out; + + if (!orig_dst_node->gw_flags) + goto out; + + ret = batadv_is_type_dhcprequest(skb, header_len); + if (!ret) + goto out; + + switch (atomic_read(&bat_priv->gw_mode)) { + case BATADV_GW_MODE_SERVER: + /* If we are a GW then we are our best GW. We can artificially + * set the tq towards ourself as the maximum value + */ + curr_tq_avg = BATADV_TQ_MAX_VALUE; + break; + case BATADV_GW_MODE_CLIENT: + curr_gw = batadv_gw_get_selected_gw_node(bat_priv); + if (!curr_gw) + goto out; + + /* packet is going to our gateway */ + if (curr_gw->orig_node == orig_dst_node) + goto out; + + /* If the dhcp packet has been sent to a different gw, + * we have to evaluate whether the old gw is still + * reliable enough + */ + neigh_curr = batadv_find_router(bat_priv, curr_gw->orig_node, + NULL); + if (!neigh_curr) + goto out; + + curr_tq_avg = neigh_curr->tq_avg; + break; + case BATADV_GW_MODE_OFF: + default: + goto out; + } + + neigh_old = batadv_find_router(bat_priv, orig_dst_node, NULL); + if (!neigh_old) + goto out; + + if (curr_tq_avg - neigh_old->tq_avg > BATADV_GW_THRESHOLD) + out_of_range = true; + +out: + if (orig_dst_node) + batadv_orig_node_free_ref(orig_dst_node); + if (curr_gw) + batadv_gw_node_free_ref(curr_gw); + if (neigh_old) + batadv_neigh_node_free_ref(neigh_old); + if (neigh_curr) + batadv_neigh_node_free_ref(neigh_curr); + return out_of_range; +} diff --git a/net/batman-adv/gateway_client.h b/net/batman-adv/gateway_client.h new file mode 100644 index 00000000..039902dc --- /dev/null +++ b/net/batman-adv/gateway_client.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2009-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_GATEWAY_CLIENT_H_ +#define _NET_BATMAN_ADV_GATEWAY_CLIENT_H_ + +void batadv_gw_deselect(struct batadv_priv *bat_priv); +void batadv_gw_election(struct batadv_priv *bat_priv); +struct batadv_orig_node * +batadv_gw_get_selected_orig(struct batadv_priv *bat_priv); +void batadv_gw_check_election(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node); +void batadv_gw_node_update(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + uint8_t new_gwflags); +void batadv_gw_node_delete(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node); +void batadv_gw_node_purge(struct batadv_priv *bat_priv); +int batadv_gw_client_seq_print_text(struct seq_file *seq, void *offset); +bool batadv_gw_is_dhcp_target(struct sk_buff *skb, unsigned int *header_len); +bool batadv_gw_out_of_range(struct batadv_priv *bat_priv, + struct sk_buff *skb, struct ethhdr *ethhdr); + +#endif /* _NET_BATMAN_ADV_GATEWAY_CLIENT_H_ */ diff --git a/net/batman-adv/gateway_common.c b/net/batman-adv/gateway_common.c new file mode 100644 index 00000000..84bb2b18 --- /dev/null +++ b/net/batman-adv/gateway_common.c @@ -0,0 +1,179 @@ +/* Copyright (C) 2009-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 "gateway_common.h" +#include "gateway_client.h" + +/* calculates the gateway class from kbit */ +static void batadv_kbit_to_gw_bandwidth(int down, int up, long *gw_srv_class) +{ + int mdown = 0, tdown, tup, difference; + uint8_t sbit, part; + + *gw_srv_class = 0; + difference = 0x0FFFFFFF; + + /* test all downspeeds */ + for (sbit = 0; sbit < 2; sbit++) { + for (part = 0; part < 16; part++) { + tdown = 32 * (sbit + 2) * (1 << part); + + if (abs(tdown - down) < difference) { + *gw_srv_class = (sbit << 7) + (part << 3); + difference = abs(tdown - down); + mdown = tdown; + } + } + } + + /* test all upspeeds */ + difference = 0x0FFFFFFF; + + for (part = 0; part < 8; part++) { + tup = ((part + 1) * (mdown)) / 8; + + if (abs(tup - up) < difference) { + *gw_srv_class = (*gw_srv_class & 0xF8) | part; + difference = abs(tup - up); + } + } +} + +/* returns the up and downspeeds in kbit, calculated from the class */ +void batadv_gw_bandwidth_to_kbit(uint8_t gw_srv_class, int *down, int *up) +{ + int sbit = (gw_srv_class & 0x80) >> 7; + int dpart = (gw_srv_class & 0x78) >> 3; + int upart = (gw_srv_class & 0x07); + + if (!gw_srv_class) { + *down = 0; + *up = 0; + return; + } + + *down = 32 * (sbit + 2) * (1 << dpart); + *up = ((upart + 1) * (*down)) / 8; +} + +static bool batadv_parse_gw_bandwidth(struct net_device *net_dev, char *buff, + int *up, int *down) +{ + int ret, multi = 1; + char *slash_ptr, *tmp_ptr; + long ldown, lup; + + slash_ptr = strchr(buff, '/'); + if (slash_ptr) + *slash_ptr = 0; + + if (strlen(buff) > 4) { + tmp_ptr = buff + strlen(buff) - 4; + + if (strnicmp(tmp_ptr, "mbit", 4) == 0) + multi = 1024; + + if ((strnicmp(tmp_ptr, "kbit", 4) == 0) || + (multi > 1)) + *tmp_ptr = '\0'; + } + + ret = kstrtol(buff, 10, &ldown); + if (ret) { + batadv_err(net_dev, + "Download speed of gateway mode invalid: %s\n", + buff); + return false; + } + + *down = ldown * multi; + + /* we also got some upload info */ + if (slash_ptr) { + multi = 1; + + if (strlen(slash_ptr + 1) > 4) { + tmp_ptr = slash_ptr + 1 - 4 + strlen(slash_ptr + 1); + + if (strnicmp(tmp_ptr, "mbit", 4) == 0) + multi = 1024; + + if ((strnicmp(tmp_ptr, "kbit", 4) == 0) || + (multi > 1)) + *tmp_ptr = '\0'; + } + + ret = kstrtol(slash_ptr + 1, 10, &lup); + if (ret) { + batadv_err(net_dev, + "Upload speed of gateway mode invalid: %s\n", + slash_ptr + 1); + return false; + } + + *up = lup * multi; + } + + return true; +} + +ssize_t batadv_gw_bandwidth_set(struct net_device *net_dev, char *buff, + size_t count) +{ + struct batadv_priv *bat_priv = netdev_priv(net_dev); + long gw_bandwidth_tmp = 0; + int up = 0, down = 0; + bool ret; + + ret = batadv_parse_gw_bandwidth(net_dev, buff, &up, &down); + if (!ret) + goto end; + + if ((!down) || (down < 256)) + down = 2000; + + if (!up) + up = down / 5; + + batadv_kbit_to_gw_bandwidth(down, up, &gw_bandwidth_tmp); + + /* the gw bandwidth we guessed above might not match the given + * speeds, hence we need to calculate it back to show the number + * that is going to be propagated + */ + batadv_gw_bandwidth_to_kbit((uint8_t)gw_bandwidth_tmp, &down, &up); + + if (atomic_read(&bat_priv->gw_bandwidth) == gw_bandwidth_tmp) + return count; + + batadv_gw_deselect(bat_priv); + batadv_info(net_dev, + "Changing gateway bandwidth from: '%i' to: '%ld' (propagating: %d%s/%d%s)\n", + atomic_read(&bat_priv->gw_bandwidth), gw_bandwidth_tmp, + (down > 2048 ? down / 1024 : down), + (down > 2048 ? "MBit" : "KBit"), + (up > 2048 ? up / 1024 : up), + (up > 2048 ? "MBit" : "KBit")); + + atomic_set(&bat_priv->gw_bandwidth, gw_bandwidth_tmp); + +end: + return count; +} diff --git a/net/batman-adv/gateway_common.h b/net/batman-adv/gateway_common.h new file mode 100644 index 00000000..509b2bf8 --- /dev/null +++ b/net/batman-adv/gateway_common.h @@ -0,0 +1,37 @@ +/* Copyright (C) 2009-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_GATEWAY_COMMON_H_ +#define _NET_BATMAN_ADV_GATEWAY_COMMON_H_ + +enum batadv_gw_modes { + BATADV_GW_MODE_OFF, + BATADV_GW_MODE_CLIENT, + BATADV_GW_MODE_SERVER, +}; + +#define BATADV_GW_MODE_OFF_NAME "off" +#define BATADV_GW_MODE_CLIENT_NAME "client" +#define BATADV_GW_MODE_SERVER_NAME "server" + +void batadv_gw_bandwidth_to_kbit(uint8_t gw_class, int *down, int *up); +ssize_t batadv_gw_bandwidth_set(struct net_device *net_dev, char *buff, + size_t count); + +#endif /* _NET_BATMAN_ADV_GATEWAY_COMMON_H_ */ diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c new file mode 100644 index 00000000..522243af --- /dev/null +++ b/net/batman-adv/hard-interface.c @@ -0,0 +1,694 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 "distributed-arp-table.h" +#include "hard-interface.h" +#include "soft-interface.h" +#include "send.h" +#include "translation-table.h" +#include "routing.h" +#include "sysfs.h" +#include "originator.h" +#include "hash.h" +#include "bridge_loop_avoidance.h" + +#include <linux/if_arp.h> +#include <linux/if_ether.h> + +void batadv_hardif_free_rcu(struct rcu_head *rcu) +{ + struct batadv_hard_iface *hard_iface; + + hard_iface = container_of(rcu, struct batadv_hard_iface, rcu); + dev_put(hard_iface->net_dev); + kfree(hard_iface); +} + +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 && + atomic_inc_not_zero(&hard_iface->refcount)) + goto out; + } + + hard_iface = NULL; + +out: + rcu_read_unlock(); + return hard_iface; +} + +/** + * 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 to be 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. + * + * Returns 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_device *parent_dev; + bool ret; + + /* check if this is a batman-adv mesh interface */ + if (batadv_softif_is_valid(net_dev)) + return true; + + /* no more parents..stop recursion */ + if (net_dev->iflink == net_dev->ifindex) + return false; + + /* recurse over the parent device */ + parent_dev = dev_get_by_index(&init_net, net_dev->iflink); + /* if we got a NULL parent_dev there is something broken.. */ + if (WARN(!parent_dev, "Cannot find parent device")) + return false; + + ret = batadv_is_on_batman_iface(parent_dev); + + if (parent_dev) + dev_put(parent_dev); + return ret; +} + +static int batadv_is_valid_iface(const struct net_device *net_dev) +{ + if (net_dev->flags & IFF_LOOPBACK) + return 0; + + if (net_dev->type != ARPHRD_ETHER) + return 0; + + if (net_dev->addr_len != ETH_ALEN) + return 0; + + /* no batman over batman */ + if (batadv_is_on_batman_iface(net_dev)) + return 0; + + return 1; +} + +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 && + atomic_inc_not_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_vis_packet *vis_packet; + struct batadv_hard_iface *primary_if; + struct sk_buff *skb; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + batadv_dat_init_own_addr(bat_priv, primary_if); + + skb = bat_priv->vis.my_info->skb_packet; + vis_packet = (struct batadv_vis_packet *)skb->data; + memcpy(vis_packet->vis_orig, primary_if->net_dev->dev_addr, ETH_ALEN); + memcpy(vis_packet->sender_orig, + primary_if->net_dev->dev_addr, ETH_ALEN); + + batadv_bla_update_orig_address(bat_priv, primary_if, oldif); +out: + if (primary_if) + batadv_hardif_free_ref(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 && !atomic_inc_not_zero(&new_hard_iface->refcount)) + new_hard_iface = NULL; + + curr_hard_iface = rcu_dereference_protected(bat_priv->primary_if, 1); + rcu_assign_pointer(bat_priv->primary_if, new_hard_iface); + + if (!new_hard_iface) + goto out; + + bat_priv->bat_algo_ops->bat_primary_iface_set(new_hard_iface); + batadv_primary_if_update_addr(bat_priv, curr_hard_iface); + +out: + if (curr_hard_iface) + batadv_hardif_free_ref(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(); +} + +int batadv_hardif_min_mtu(struct net_device *soft_iface) +{ + const struct batadv_priv *bat_priv = netdev_priv(soft_iface); + const struct batadv_hard_iface *hard_iface; + /* allow big frames if all devices are capable to do so + * (have MTU > 1500 + BAT_HEADER_LEN) + */ + int min_mtu = ETH_DATA_LEN; + + if (atomic_read(&bat_priv->fragmentation)) + goto out; + + 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 - BATADV_HEADER_LEN, + min_mtu); + } + rcu_read_unlock(); +out: + return min_mtu; +} + +/* adjusts the MTU if a new interface with a smaller MTU appeared. */ +void batadv_update_min_mtu(struct net_device *soft_iface) +{ + int min_mtu; + + min_mtu = batadv_hardif_min_mtu(soft_iface); + if (soft_iface->mtu != min_mtu) + soft_iface->mtu = min_mtu; +} + +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->bat_algo_ops->bat_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); + +out: + if (primary_if) + batadv_hardif_free_ref(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_master_del_slave - remove hard_iface from the current master interface + * @slave: the interface enslaved in another master + * @master: the master from which slave has to be removed + * + * Invoke ndo_del_slave on master passing slave as argument. In this way slave + * is free'd and master can correctly change its internal state. + * Return 0 on success, a negative value representing the error otherwise + */ +static int batadv_master_del_slave(struct batadv_hard_iface *slave, + struct net_device *master) +{ + int ret; + + if (!master) + return 0; + + ret = -EBUSY; + if (master->netdev_ops->ndo_del_slave) + ret = master->netdev_ops->ndo_del_slave(master, slave->net_dev); + + return ret; +} + +int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, + const char *iface_name) +{ + struct batadv_priv *bat_priv; + struct net_device *soft_iface, *master; + __be16 ethertype = __constant_htons(ETH_P_BATMAN); + int ret; + + if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) + goto out; + + if (!atomic_inc_not_zero(&hard_iface->refcount)) + goto out; + + soft_iface = dev_get_by_name(&init_net, iface_name); + + if (!soft_iface) { + soft_iface = batadv_softif_create(iface_name); + + if (!soft_iface) { + ret = -ENOMEM; + goto err; + } + + /* dev_get_by_name() increases the reference counter for us */ + dev_hold(soft_iface); + } + + if (!batadv_softif_is_valid(soft_iface)) { + pr_err("Can't create batman mesh interface %s: already exists as regular interface\n", + soft_iface->name); + ret = -EINVAL; + goto err_dev; + } + + /* check if the interface is enslaved in another virtual one and + * in that case unlink it first + */ + master = netdev_master_upper_dev_get(hard_iface->net_dev); + ret = batadv_master_del_slave(hard_iface, master); + if (ret) + goto err_dev; + + 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); + if (ret) + goto err_dev; + + ret = bat_priv->bat_algo_ops->bat_iface_enable(hard_iface); + if (ret < 0) + goto err_upper; + + hard_iface->if_num = bat_priv->num_ifaces; + bat_priv->num_ifaces++; + hard_iface->if_status = BATADV_IF_INACTIVE; + ret = batadv_orig_hash_add_if(hard_iface, bat_priv->num_ifaces); + if (ret < 0) { + bat_priv->bat_algo_ops->bat_iface_disable(hard_iface); + bat_priv->num_ifaces--; + hard_iface->if_status = BATADV_IF_NOT_IN_USE; + 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); + + atomic_set(&hard_iface->frag_seqno, 1); + 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 + BATADV_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 %zi would solve the problem.\n", + hard_iface->net_dev->name, hard_iface->net_dev->mtu, + ETH_DATA_LEN + BATADV_HEADER_LEN); + + if (!atomic_read(&bat_priv->fragmentation) && + hard_iface->net_dev->mtu < ETH_DATA_LEN + BATADV_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 %zi.\n", + hard_iface->net_dev->name, hard_iface->net_dev->mtu, + ETH_DATA_LEN + BATADV_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); + + /* begin scheduling originator messages on that interface */ + batadv_schedule_bat_ogm(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); +err: + batadv_hardif_free_ref(hard_iface); + return ret; +} + +void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface, + enum batadv_hard_if_cleanup autodel) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + struct batadv_hard_iface *primary_if = NULL; + + if (hard_iface->if_status == BATADV_IF_ACTIVE) + 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); + + bat_priv->num_ifaces--; + batadv_orig_hash_del_if(hard_iface, bat_priv->num_ifaces); + + 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); + + if (new_if) + batadv_hardif_free_ref(new_if); + } + + bat_priv->bat_algo_ops->bat_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); + + /* nobody uses this interface anymore */ + if (!bat_priv->num_ifaces && autodel == BATADV_IF_CLEANUP_AUTO) + batadv_softif_destroy_sysfs(hard_iface->soft_iface); + + netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->soft_iface); + hard_iface->soft_iface = NULL; + batadv_hardif_free_ref(hard_iface); + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); +} + +/** + * batadv_hardif_remove_interface_finish - cleans up the remains of a hardif + * @work: work queue item + * + * Free the parts of the hard interface which can not be removed under + * rtnl lock (to prevent deadlock situations). + */ +static void batadv_hardif_remove_interface_finish(struct work_struct *work) +{ + struct batadv_hard_iface *hard_iface; + + hard_iface = container_of(work, struct batadv_hard_iface, + cleanup_work); + + batadv_sysfs_del_hardif(&hard_iface->hardif_obj); + batadv_hardif_free_ref(hard_iface); +} + +static struct batadv_hard_iface * +batadv_hardif_add_interface(struct net_device *net_dev) +{ + struct batadv_hard_iface *hard_iface; + int ret; + + ASSERT_RTNL(); + + ret = batadv_is_valid_iface(net_dev); + if (ret != 1) + goto out; + + dev_hold(net_dev); + + hard_iface = kmalloc(sizeof(*hard_iface), GFP_ATOMIC); + if (!hard_iface) + goto release_dev; + + ret = batadv_sysfs_add_hardif(&hard_iface->hardif_obj, net_dev); + if (ret) + goto free_if; + + hard_iface->if_num = -1; + 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_WORK(&hard_iface->cleanup_work, + batadv_hardif_remove_interface_finish); + + /* extra reference for return */ + atomic_set(&hard_iface->refcount, 2); + + batadv_check_known_mac_addr(hard_iface->net_dev); + list_add_tail_rcu(&hard_iface->list, &batadv_hardif_list); + + /* This can't be called via a bat_priv callback because + * we have no bat_priv yet. + */ + atomic_set(&hard_iface->bat_iv.ogm_seqno, 1); + hard_iface->bat_iv.ogm_buff = NULL; + + return hard_iface; + +free_if: + kfree(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, + BATADV_IF_CLEANUP_AUTO); + + if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) + return; + + hard_iface->if_status = BATADV_IF_TO_BE_REMOVED; + queue_work(batadv_event_workqueue, &hard_iface->cleanup_work); +} + +void batadv_hardif_remove_interfaces(void) +{ + struct batadv_hard_iface *hard_iface, *hard_iface_tmp; + + rtnl_lock(); + list_for_each_entry_safe(hard_iface, hard_iface_tmp, + &batadv_hardif_list, list) { + list_del_rcu(&hard_iface->list); + batadv_hardif_remove_interface(hard_iface); + } + rtnl_unlock(); +} + +static int batadv_hard_if_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct net_device *net_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) && event == NETDEV_REGISTER) { + batadv_sysfs_add_meshif(net_dev); + return NOTIFY_DONE; + } + + hard_iface = batadv_hardif_get_by_netdev(net_dev); + if (!hard_iface && event == NETDEV_REGISTER) + 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: + list_del_rcu(&hard_iface->list); + + 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->bat_algo_ops->bat_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; + default: + break; + } + +hardif_put: + batadv_hardif_free_ref(hard_iface); +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return NOTIFY_DONE; +} + +/* This function returns true if the interface represented by ifindex is a + * 802.11 wireless device + */ +bool batadv_is_wifi_iface(int ifindex) +{ + struct net_device *net_device = NULL; + bool ret = false; + + if (ifindex == BATADV_NULL_IFINDEX) + goto out; + + net_device = dev_get_by_index(&init_net, ifindex); + if (!net_device) + goto out; + +#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) + ret = true; + else +#endif + /* cfg80211 drivers have to set ieee80211_ptr */ + if (net_device->ieee80211_ptr) + ret = true; +out: + if (net_device) + dev_put(net_device); + return ret; +} + +struct notifier_block batadv_hard_if_notifier = { + .notifier_call = batadv_hard_if_event, +}; diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h new file mode 100644 index 00000000..49892881 --- /dev/null +++ b/net/batman-adv/hard-interface.h @@ -0,0 +1,81 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_HARD_INTERFACE_H_ +#define _NET_BATMAN_ADV_HARD_INTERFACE_H_ + +enum batadv_hard_if_state { + BATADV_IF_NOT_IN_USE, + BATADV_IF_TO_BE_REMOVED, + BATADV_IF_INACTIVE, + BATADV_IF_ACTIVE, + BATADV_IF_TO_BE_ACTIVATED, + BATADV_IF_I_WANT_YOU, +}; + +/** + * enum batadv_hard_if_cleanup - Cleanup modi for soft_iface after slave removal + * @BATADV_IF_CLEANUP_KEEP: Don't automatically delete soft-interface + * @BATADV_IF_CLEANUP_AUTO: Delete soft-interface after last slave was removed + */ +enum batadv_hard_if_cleanup { + BATADV_IF_CLEANUP_KEEP, + BATADV_IF_CLEANUP_AUTO, +}; + +extern struct notifier_block batadv_hard_if_notifier; + +struct batadv_hard_iface* +batadv_hardif_get_by_netdev(const struct net_device *net_dev); +int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface, + const char *iface_name); +void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface, + enum batadv_hard_if_cleanup autodel); +void batadv_hardif_remove_interfaces(void); +int batadv_hardif_min_mtu(struct net_device *soft_iface); +void batadv_update_min_mtu(struct net_device *soft_iface); +void batadv_hardif_free_rcu(struct rcu_head *rcu); +bool batadv_is_wifi_iface(int ifindex); + +static inline void +batadv_hardif_free_ref(struct batadv_hard_iface *hard_iface) +{ + if (atomic_dec_and_test(&hard_iface->refcount)) + call_rcu(&hard_iface->rcu, batadv_hardif_free_rcu); +} + +static inline struct batadv_hard_iface * +batadv_primary_if_get_selected(struct batadv_priv *bat_priv) +{ + struct batadv_hard_iface *hard_iface; + + rcu_read_lock(); + hard_iface = rcu_dereference(bat_priv->primary_if); + if (!hard_iface) + goto out; + + if (!atomic_inc_not_zero(&hard_iface->refcount)) + hard_iface = NULL; + +out: + rcu_read_unlock(); + return hard_iface; +} + +#endif /* _NET_BATMAN_ADV_HARD_INTERFACE_H_ */ diff --git a/net/batman-adv/hash.c b/net/batman-adv/hash.c new file mode 100644 index 00000000..7198dafd --- /dev/null +++ b/net/batman-adv/hash.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2006-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich, Marek Lindner + * + * 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 "hash.h" + +/* clears the hash */ +static void batadv_hash_init(struct batadv_hashtable *hash) +{ + uint32_t i; + + for (i = 0; i < hash->size; i++) { + INIT_HLIST_HEAD(&hash->table[i]); + spin_lock_init(&hash->list_locks[i]); + } +} + +/* free only the hashtable and the hash itself. */ +void batadv_hash_destroy(struct batadv_hashtable *hash) +{ + kfree(hash->list_locks); + kfree(hash->table); + kfree(hash); +} + +/* allocates and clears the hash */ +struct batadv_hashtable *batadv_hash_new(uint32_t size) +{ + struct batadv_hashtable *hash; + + hash = kmalloc(sizeof(*hash), GFP_ATOMIC); + if (!hash) + return NULL; + + hash->table = kmalloc(sizeof(*hash->table) * size, GFP_ATOMIC); + if (!hash->table) + goto free_hash; + + hash->list_locks = kmalloc(sizeof(*hash->list_locks) * size, + GFP_ATOMIC); + if (!hash->list_locks) + goto free_table; + + hash->size = size; + batadv_hash_init(hash); + return hash; + +free_table: + kfree(hash->table); +free_hash: + kfree(hash); + return NULL; +} + +void batadv_hash_set_lock_class(struct batadv_hashtable *hash, + struct lock_class_key *key) +{ + uint32_t i; + + for (i = 0; i < hash->size; i++) + lockdep_set_class(&hash->list_locks[i], key); +} diff --git a/net/batman-adv/hash.h b/net/batman-adv/hash.h new file mode 100644 index 00000000..1b4da72f --- /dev/null +++ b/net/batman-adv/hash.h @@ -0,0 +1,189 @@ +/* Copyright (C) 2006-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich, Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_HASH_H_ +#define _NET_BATMAN_ADV_HASH_H_ + +#include <linux/list.h> + +/* callback to a compare function. should compare 2 element datas for their + * keys, return 0 if same and not 0 if not same + */ +typedef int (*batadv_hashdata_compare_cb)(const struct hlist_node *, + const void *); + +/* the hashfunction, should return an index + * based on the key in the data of the first + * argument and the size the second + */ +typedef uint32_t (*batadv_hashdata_choose_cb)(const void *, uint32_t); +typedef void (*batadv_hashdata_free_cb)(struct hlist_node *, void *); + +struct batadv_hashtable { + struct hlist_head *table; /* the hashtable itself with the buckets */ + spinlock_t *list_locks; /* spinlock for each hash list entry */ + uint32_t size; /* size of hashtable */ +}; + +/* allocates and clears the hash */ +struct batadv_hashtable *batadv_hash_new(uint32_t size); + +/* set class key for all locks */ +void batadv_hash_set_lock_class(struct batadv_hashtable *hash, + struct lock_class_key *key); + +/* free only the hashtable and the hash itself. */ +void batadv_hash_destroy(struct batadv_hashtable *hash); + +/* remove the hash structure. if hashdata_free_cb != NULL, this function will be + * called to remove the elements inside of the hash. if you don't remove the + * elements, memory might be leaked. + */ +static inline void batadv_hash_delete(struct batadv_hashtable *hash, + batadv_hashdata_free_cb free_cb, + void *arg) +{ + struct hlist_head *head; + struct hlist_node *node, *node_tmp; + spinlock_t *list_lock; /* spinlock to protect write access */ + 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); + hlist_for_each_safe(node, node_tmp, head) { + hlist_del_rcu(node); + + if (free_cb) + free_cb(node, arg); + } + spin_unlock_bh(list_lock); + } + + batadv_hash_destroy(hash); +} + +/** + * batadv_hash_bytes - hash some bytes and add them to the previous hash + * @hash: previous hash value + * @data: data to be hashed + * @size: number of bytes to be hashed + * + * Returns the new hash value. + */ +static inline uint32_t batadv_hash_bytes(uint32_t hash, const void *data, + uint32_t size) +{ + const unsigned char *key = data; + int i; + + for (i = 0; i < size; i++) { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + return hash; +} + +/** + * batadv_hash_add - adds data to the hashtable + * @hash: storage hash table + * @compare: callback to determine if 2 hash elements are identical + * @choose: callback calculating the hash index + * @data: data passed to the aforementioned callbacks as argument + * @data_node: to be added element + * + * Returns 0 on success, 1 if the element already is in the hash + * and -1 on error. + */ +static inline int batadv_hash_add(struct batadv_hashtable *hash, + batadv_hashdata_compare_cb compare, + batadv_hashdata_choose_cb choose, + const void *data, + struct hlist_node *data_node) +{ + uint32_t index; + int ret = -1; + struct hlist_head *head; + struct hlist_node *node; + spinlock_t *list_lock; /* spinlock to protect write access */ + + if (!hash) + goto out; + + index = choose(data, hash->size); + head = &hash->table[index]; + list_lock = &hash->list_locks[index]; + + spin_lock_bh(list_lock); + + hlist_for_each(node, head) { + if (!compare(node, data)) + continue; + + ret = 1; + goto unlock; + } + + /* no duplicate found in list, add new element */ + hlist_add_head_rcu(data_node, head); + + ret = 0; + +unlock: + spin_unlock_bh(list_lock); +out: + return ret; +} + +/* removes data from hash, if found. returns pointer do data on success, so you + * can remove the used structure yourself, or NULL on error . data could be the + * structure you use with just the key filled, we just need the key for + * comparing. + */ +static inline void *batadv_hash_remove(struct batadv_hashtable *hash, + batadv_hashdata_compare_cb compare, + batadv_hashdata_choose_cb choose, + void *data) +{ + uint32_t index; + struct hlist_node *node; + struct hlist_head *head; + void *data_save = NULL; + + index = choose(data, hash->size); + head = &hash->table[index]; + + spin_lock_bh(&hash->list_locks[index]); + hlist_for_each(node, head) { + if (!compare(node, data)) + continue; + + data_save = node; + hlist_del_rcu(node); + break; + } + spin_unlock_bh(&hash->list_locks[index]); + + return data_save; +} + +#endif /* _NET_BATMAN_ADV_HASH_H_ */ diff --git a/net/batman-adv/icmp_socket.c b/net/batman-adv/icmp_socket.c new file mode 100644 index 00000000..0ba6c899 --- /dev/null +++ b/net/batman-adv/icmp_socket.c @@ -0,0 +1,352 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 <linux/debugfs.h> +#include <linux/slab.h> +#include "icmp_socket.h" +#include "send.h" +#include "hash.h" +#include "originator.h" +#include "hard-interface.h" + +static struct batadv_socket_client *batadv_socket_client_hash[256]; + +static void batadv_socket_add_packet(struct batadv_socket_client *socket_client, + struct batadv_icmp_packet_rr *icmp_packet, + size_t icmp_len); + +void batadv_socket_init(void) +{ + memset(batadv_socket_client_hash, 0, sizeof(batadv_socket_client_hash)); +} + +static int batadv_socket_open(struct inode *inode, struct file *file) +{ + unsigned int i; + struct batadv_socket_client *socket_client; + + if (!try_module_get(THIS_MODULE)) + return -EBUSY; + + nonseekable_open(inode, file); + + socket_client = kmalloc(sizeof(*socket_client), GFP_KERNEL); + if (!socket_client) { + module_put(THIS_MODULE); + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(batadv_socket_client_hash); i++) { + if (!batadv_socket_client_hash[i]) { + batadv_socket_client_hash[i] = socket_client; + break; + } + } + + if (i == ARRAY_SIZE(batadv_socket_client_hash)) { + pr_err("Error - can't add another packet client: maximum number of clients reached\n"); + kfree(socket_client); + module_put(THIS_MODULE); + return -EXFULL; + } + + INIT_LIST_HEAD(&socket_client->queue_list); + socket_client->queue_len = 0; + socket_client->index = i; + socket_client->bat_priv = inode->i_private; + spin_lock_init(&socket_client->lock); + init_waitqueue_head(&socket_client->queue_wait); + + file->private_data = socket_client; + + return 0; +} + +static int batadv_socket_release(struct inode *inode, struct file *file) +{ + struct batadv_socket_client *socket_client = file->private_data; + struct batadv_socket_packet *socket_packet; + struct list_head *list_pos, *list_pos_tmp; + + spin_lock_bh(&socket_client->lock); + + /* for all packets in the queue ... */ + list_for_each_safe(list_pos, list_pos_tmp, &socket_client->queue_list) { + socket_packet = list_entry(list_pos, + struct batadv_socket_packet, list); + + list_del(list_pos); + kfree(socket_packet); + } + + batadv_socket_client_hash[socket_client->index] = NULL; + spin_unlock_bh(&socket_client->lock); + + kfree(socket_client); + module_put(THIS_MODULE); + + return 0; +} + +static ssize_t batadv_socket_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct batadv_socket_client *socket_client = file->private_data; + struct batadv_socket_packet *socket_packet; + size_t packet_len; + int error; + + if ((file->f_flags & O_NONBLOCK) && (socket_client->queue_len == 0)) + return -EAGAIN; + + if ((!buf) || (count < sizeof(struct batadv_icmp_packet))) + return -EINVAL; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + error = wait_event_interruptible(socket_client->queue_wait, + socket_client->queue_len); + + if (error) + return error; + + spin_lock_bh(&socket_client->lock); + + socket_packet = list_first_entry(&socket_client->queue_list, + struct batadv_socket_packet, list); + list_del(&socket_packet->list); + socket_client->queue_len--; + + spin_unlock_bh(&socket_client->lock); + + packet_len = min(count, socket_packet->icmp_len); + error = copy_to_user(buf, &socket_packet->icmp_packet, packet_len); + + kfree(socket_packet); + + if (error) + return -EFAULT; + + return packet_len; +} + +static ssize_t batadv_socket_write(struct file *file, const char __user *buff, + size_t len, loff_t *off) +{ + struct batadv_socket_client *socket_client = file->private_data; + struct batadv_priv *bat_priv = socket_client->bat_priv; + struct batadv_hard_iface *primary_if = NULL; + struct sk_buff *skb; + struct batadv_icmp_packet_rr *icmp_packet; + + struct batadv_orig_node *orig_node = NULL; + struct batadv_neigh_node *neigh_node = NULL; + size_t packet_len = sizeof(struct batadv_icmp_packet); + + if (len < sizeof(struct batadv_icmp_packet)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Error - can't send packet from char device: invalid packet size\n"); + return -EINVAL; + } + + primary_if = batadv_primary_if_get_selected(bat_priv); + + if (!primary_if) { + len = -EFAULT; + goto out; + } + + if (len >= sizeof(struct batadv_icmp_packet_rr)) + packet_len = sizeof(struct batadv_icmp_packet_rr); + + skb = dev_alloc_skb(packet_len + ETH_HLEN + NET_IP_ALIGN); + if (!skb) { + len = -ENOMEM; + goto out; + } + + skb_reserve(skb, ETH_HLEN + NET_IP_ALIGN); + icmp_packet = (struct batadv_icmp_packet_rr *)skb_put(skb, packet_len); + + if (copy_from_user(icmp_packet, buff, packet_len)) { + len = -EFAULT; + goto free_skb; + } + + if (icmp_packet->header.packet_type != BATADV_ICMP) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Error - can't send packet from char device: got bogus packet type (expected: BAT_ICMP)\n"); + len = -EINVAL; + goto free_skb; + } + + if (icmp_packet->msg_type != BATADV_ECHO_REQUEST) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Error - can't send packet from char device: got bogus message type (expected: ECHO_REQUEST)\n"); + len = -EINVAL; + goto free_skb; + } + + icmp_packet->uid = socket_client->index; + + if (icmp_packet->header.version != BATADV_COMPAT_VERSION) { + icmp_packet->msg_type = BATADV_PARAMETER_PROBLEM; + icmp_packet->header.version = BATADV_COMPAT_VERSION; + batadv_socket_add_packet(socket_client, icmp_packet, + packet_len); + goto free_skb; + } + + if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) + goto dst_unreach; + + orig_node = batadv_orig_hash_find(bat_priv, icmp_packet->dst); + if (!orig_node) + goto dst_unreach; + + neigh_node = batadv_orig_node_get_router(orig_node); + if (!neigh_node) + goto dst_unreach; + + if (!neigh_node->if_incoming) + goto dst_unreach; + + if (neigh_node->if_incoming->if_status != BATADV_IF_ACTIVE) + goto dst_unreach; + + memcpy(icmp_packet->orig, + primary_if->net_dev->dev_addr, ETH_ALEN); + + if (packet_len == sizeof(struct batadv_icmp_packet_rr)) + memcpy(icmp_packet->rr, + neigh_node->if_incoming->net_dev->dev_addr, ETH_ALEN); + + batadv_send_skb_packet(skb, neigh_node->if_incoming, neigh_node->addr); + goto out; + +dst_unreach: + icmp_packet->msg_type = BATADV_DESTINATION_UNREACHABLE; + batadv_socket_add_packet(socket_client, icmp_packet, packet_len); +free_skb: + kfree_skb(skb); +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (neigh_node) + batadv_neigh_node_free_ref(neigh_node); + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return len; +} + +static unsigned int batadv_socket_poll(struct file *file, poll_table *wait) +{ + struct batadv_socket_client *socket_client = file->private_data; + + poll_wait(file, &socket_client->queue_wait, wait); + + if (socket_client->queue_len > 0) + return POLLIN | POLLRDNORM; + + return 0; +} + +static const struct file_operations batadv_fops = { + .owner = THIS_MODULE, + .open = batadv_socket_open, + .release = batadv_socket_release, + .read = batadv_socket_read, + .write = batadv_socket_write, + .poll = batadv_socket_poll, + .llseek = no_llseek, +}; + +int batadv_socket_setup(struct batadv_priv *bat_priv) +{ + struct dentry *d; + + if (!bat_priv->debug_dir) + goto err; + + d = debugfs_create_file(BATADV_ICMP_SOCKET, S_IFREG | S_IWUSR | S_IRUSR, + bat_priv->debug_dir, bat_priv, &batadv_fops); + if (!d) + goto err; + + return 0; + +err: + return -ENOMEM; +} + +static void batadv_socket_add_packet(struct batadv_socket_client *socket_client, + struct batadv_icmp_packet_rr *icmp_packet, + size_t icmp_len) +{ + struct batadv_socket_packet *socket_packet; + + socket_packet = kmalloc(sizeof(*socket_packet), GFP_ATOMIC); + + if (!socket_packet) + return; + + INIT_LIST_HEAD(&socket_packet->list); + memcpy(&socket_packet->icmp_packet, icmp_packet, icmp_len); + socket_packet->icmp_len = icmp_len; + + spin_lock_bh(&socket_client->lock); + + /* while waiting for the lock the socket_client could have been + * deleted + */ + if (!batadv_socket_client_hash[icmp_packet->uid]) { + spin_unlock_bh(&socket_client->lock); + kfree(socket_packet); + return; + } + + list_add_tail(&socket_packet->list, &socket_client->queue_list); + socket_client->queue_len++; + + if (socket_client->queue_len > 100) { + socket_packet = list_first_entry(&socket_client->queue_list, + struct batadv_socket_packet, + list); + + list_del(&socket_packet->list); + kfree(socket_packet); + socket_client->queue_len--; + } + + spin_unlock_bh(&socket_client->lock); + + wake_up(&socket_client->queue_wait); +} + +void batadv_socket_receive_packet(struct batadv_icmp_packet_rr *icmp_packet, + size_t icmp_len) +{ + struct batadv_socket_client *hash; + + hash = batadv_socket_client_hash[icmp_packet->uid]; + if (hash) + batadv_socket_add_packet(hash, icmp_packet, icmp_len); +} diff --git a/net/batman-adv/icmp_socket.h b/net/batman-adv/icmp_socket.h new file mode 100644 index 00000000..1fcca37b --- /dev/null +++ b/net/batman-adv/icmp_socket.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_ICMP_SOCKET_H_ +#define _NET_BATMAN_ADV_ICMP_SOCKET_H_ + +#define BATADV_ICMP_SOCKET "socket" + +void batadv_socket_init(void); +int batadv_socket_setup(struct batadv_priv *bat_priv); +void batadv_socket_receive_packet(struct batadv_icmp_packet_rr *icmp_packet, + size_t icmp_len); + +#endif /* _NET_BATMAN_ADV_ICMP_SOCKET_H_ */ diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c new file mode 100644 index 00000000..f1cb1f56 --- /dev/null +++ b/net/batman-adv/main.c @@ -0,0 +1,520 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 <linux/crc32c.h> +#include <linux/highmem.h> +#include "main.h" +#include "sysfs.h" +#include "debugfs.h" +#include "routing.h" +#include "send.h" +#include "originator.h" +#include "soft-interface.h" +#include "icmp_socket.h" +#include "translation-table.h" +#include "hard-interface.h" +#include "gateway_client.h" +#include "bridge_loop_avoidance.h" +#include "distributed-arp-table.h" +#include "vis.h" +#include "hash.h" +#include "bat_algo.h" +#include "network-coding.h" + + +/* List manipulations on hardif_list have to be rtnl_lock()'ed, + * list traversals just rcu-locked + */ +struct list_head batadv_hardif_list; +static int (*batadv_rx_handler[256])(struct sk_buff *, + struct batadv_hard_iface *); +char batadv_routing_algo[20] = "BATMAN_IV"; +static struct hlist_head batadv_algo_list; + +unsigned char batadv_broadcast_addr[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +struct workqueue_struct *batadv_event_workqueue; + +static void batadv_recv_handler_init(void); + +static int __init batadv_init(void) +{ + INIT_LIST_HEAD(&batadv_hardif_list); + INIT_HLIST_HEAD(&batadv_algo_list); + + batadv_recv_handler_init(); + + batadv_iv_init(); + batadv_nc_init(); + + batadv_event_workqueue = create_singlethread_workqueue("bat_events"); + + if (!batadv_event_workqueue) + return -ENOMEM; + + batadv_socket_init(); + batadv_debugfs_init(); + + register_netdevice_notifier(&batadv_hard_if_notifier); + rtnl_link_register(&batadv_link_ops); + + pr_info("B.A.T.M.A.N. advanced %s (compatibility version %i) loaded\n", + BATADV_SOURCE_VERSION, BATADV_COMPAT_VERSION); + + return 0; +} + +static void __exit batadv_exit(void) +{ + batadv_debugfs_destroy(); + rtnl_link_unregister(&batadv_link_ops); + unregister_netdevice_notifier(&batadv_hard_if_notifier); + batadv_hardif_remove_interfaces(); + + flush_workqueue(batadv_event_workqueue); + destroy_workqueue(batadv_event_workqueue); + batadv_event_workqueue = NULL; + + rcu_barrier(); +} + +int batadv_mesh_init(struct net_device *soft_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(soft_iface); + int ret; + + spin_lock_init(&bat_priv->forw_bat_list_lock); + spin_lock_init(&bat_priv->forw_bcast_list_lock); + spin_lock_init(&bat_priv->tt.changes_list_lock); + spin_lock_init(&bat_priv->tt.req_list_lock); + spin_lock_init(&bat_priv->tt.roam_list_lock); + spin_lock_init(&bat_priv->tt.last_changeset_lock); + spin_lock_init(&bat_priv->gw.list_lock); + spin_lock_init(&bat_priv->vis.hash_lock); + spin_lock_init(&bat_priv->vis.list_lock); + + INIT_HLIST_HEAD(&bat_priv->forw_bat_list); + INIT_HLIST_HEAD(&bat_priv->forw_bcast_list); + INIT_HLIST_HEAD(&bat_priv->gw.list); + INIT_LIST_HEAD(&bat_priv->tt.changes_list); + INIT_LIST_HEAD(&bat_priv->tt.req_list); + INIT_LIST_HEAD(&bat_priv->tt.roam_list); + + ret = batadv_originator_init(bat_priv); + if (ret < 0) + goto err; + + ret = batadv_tt_init(bat_priv); + if (ret < 0) + goto err; + + batadv_tt_local_add(soft_iface, soft_iface->dev_addr, + BATADV_NULL_IFINDEX); + + ret = batadv_vis_init(bat_priv); + if (ret < 0) + goto err; + + ret = batadv_bla_init(bat_priv); + if (ret < 0) + goto err; + + ret = batadv_dat_init(bat_priv); + if (ret < 0) + goto err; + + ret = batadv_nc_mesh_init(bat_priv); + if (ret < 0) + goto err; + + atomic_set(&bat_priv->gw.reselect, 0); + atomic_set(&bat_priv->mesh_state, BATADV_MESH_ACTIVE); + + return 0; + +err: + batadv_mesh_free(soft_iface); + return ret; +} + +void batadv_mesh_free(struct net_device *soft_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(soft_iface); + + atomic_set(&bat_priv->mesh_state, BATADV_MESH_DEACTIVATING); + + batadv_purge_outstanding_packets(bat_priv, NULL); + + batadv_vis_quit(bat_priv); + + batadv_gw_node_purge(bat_priv); + batadv_nc_mesh_free(bat_priv); + batadv_dat_free(bat_priv); + batadv_bla_free(bat_priv); + + /* Free the TT and the originator tables only after having terminated + * all the other depending components which may use these structures for + * their purposes. + */ + batadv_tt_free(bat_priv); + + /* Since the originator table clean up routine is accessing the TT + * tables as well, it has to be invoked after the TT tables have been + * freed and marked as empty. This ensures that no cleanup RCU callbacks + * accessing the TT data are scheduled for later execution. + */ + batadv_originator_free(bat_priv); + + free_percpu(bat_priv->bat_counters); + bat_priv->bat_counters = NULL; + + atomic_set(&bat_priv->mesh_state, BATADV_MESH_INACTIVE); +} + +/** + * batadv_is_my_mac - check if the given mac address belongs to any of the real + * interfaces in the current mesh + * @bat_priv: the bat priv with all the soft interface information + * @addr: the address to check + */ +int batadv_is_my_mac(struct batadv_priv *bat_priv, const uint8_t *addr) +{ + 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) + continue; + + if (hard_iface->soft_iface != bat_priv->soft_iface) + continue; + + if (batadv_compare_eth(hard_iface->net_dev->dev_addr, addr)) { + rcu_read_unlock(); + return 1; + } + } + rcu_read_unlock(); + return 0; +} + +/** + * batadv_seq_print_text_primary_if_get - called from debugfs table printing + * function that requires the primary interface + * @seq: debugfs table seq_file struct + * + * Returns primary interface if found or NULL otherwise. + */ +struct batadv_hard_iface * +batadv_seq_print_text_primary_if_get(struct seq_file *seq) +{ + struct net_device *net_dev = (struct net_device *)seq->private; + struct batadv_priv *bat_priv = netdev_priv(net_dev); + struct batadv_hard_iface *primary_if; + + primary_if = batadv_primary_if_get_selected(bat_priv); + + if (!primary_if) { + seq_printf(seq, + "BATMAN mesh %s disabled - please specify interfaces to enable it\n", + net_dev->name); + goto out; + } + + if (primary_if->if_status == BATADV_IF_ACTIVE) + goto out; + + seq_printf(seq, + "BATMAN mesh %s disabled - primary interface not active\n", + net_dev->name); + batadv_hardif_free_ref(primary_if); + primary_if = NULL; + +out: + return primary_if; +} + +static int batadv_recv_unhandled_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + return NET_RX_DROP; +} + +/* incoming packets with the batman ethertype received on any active hard + * interface + */ +int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype, + struct net_device *orig_dev) +{ + struct batadv_priv *bat_priv; + struct batadv_ogm_packet *batadv_ogm_packet; + struct batadv_hard_iface *hard_iface; + uint8_t idx; + int ret; + + hard_iface = container_of(ptype, struct batadv_hard_iface, + batman_adv_ptype); + skb = skb_share_check(skb, GFP_ATOMIC); + + /* skb was released by skb_share_check() */ + if (!skb) + goto err_out; + + /* packet should hold at least type and version */ + if (unlikely(!pskb_may_pull(skb, 2))) + goto err_free; + + /* expect a valid ethernet header here. */ + if (unlikely(skb->mac_len != ETH_HLEN || !skb_mac_header(skb))) + goto err_free; + + if (!hard_iface->soft_iface) + goto err_free; + + bat_priv = netdev_priv(hard_iface->soft_iface); + + if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) + goto err_free; + + /* discard frames on not active interfaces */ + if (hard_iface->if_status != BATADV_IF_ACTIVE) + goto err_free; + + batadv_ogm_packet = (struct batadv_ogm_packet *)skb->data; + + if (batadv_ogm_packet->header.version != BATADV_COMPAT_VERSION) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Drop packet: incompatible batman version (%i)\n", + batadv_ogm_packet->header.version); + goto err_free; + } + + /* all receive handlers return whether they received or reused + * the supplied skb. if not, we have to free the skb. + */ + idx = batadv_ogm_packet->header.packet_type; + ret = (*batadv_rx_handler[idx])(skb, hard_iface); + + if (ret == NET_RX_DROP) + kfree_skb(skb); + + /* return NET_RX_SUCCESS in any case as we + * most probably dropped the packet for + * routing-logical reasons. + */ + return NET_RX_SUCCESS; + +err_free: + kfree_skb(skb); +err_out: + return NET_RX_DROP; +} + +static void batadv_recv_handler_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(batadv_rx_handler); i++) + batadv_rx_handler[i] = batadv_recv_unhandled_packet; + + /* batman icmp packet */ + batadv_rx_handler[BATADV_ICMP] = batadv_recv_icmp_packet; + /* unicast with 4 addresses packet */ + batadv_rx_handler[BATADV_UNICAST_4ADDR] = batadv_recv_unicast_packet; + /* unicast packet */ + batadv_rx_handler[BATADV_UNICAST] = batadv_recv_unicast_packet; + /* fragmented unicast packet */ + batadv_rx_handler[BATADV_UNICAST_FRAG] = batadv_recv_ucast_frag_packet; + /* broadcast packet */ + batadv_rx_handler[BATADV_BCAST] = batadv_recv_bcast_packet; + /* vis packet */ + batadv_rx_handler[BATADV_VIS] = batadv_recv_vis_packet; + /* Translation table query (request or response) */ + batadv_rx_handler[BATADV_TT_QUERY] = batadv_recv_tt_query; + /* Roaming advertisement */ + batadv_rx_handler[BATADV_ROAM_ADV] = batadv_recv_roam_adv; +} + +int +batadv_recv_handler_register(uint8_t packet_type, + int (*recv_handler)(struct sk_buff *, + struct batadv_hard_iface *)) +{ + if (batadv_rx_handler[packet_type] != &batadv_recv_unhandled_packet) + return -EBUSY; + + batadv_rx_handler[packet_type] = recv_handler; + return 0; +} + +void batadv_recv_handler_unregister(uint8_t packet_type) +{ + batadv_rx_handler[packet_type] = batadv_recv_unhandled_packet; +} + +static struct batadv_algo_ops *batadv_algo_get(char *name) +{ + struct batadv_algo_ops *bat_algo_ops = NULL, *bat_algo_ops_tmp; + + hlist_for_each_entry(bat_algo_ops_tmp, &batadv_algo_list, list) { + if (strcmp(bat_algo_ops_tmp->name, name) != 0) + continue; + + bat_algo_ops = bat_algo_ops_tmp; + break; + } + + return bat_algo_ops; +} + +int batadv_algo_register(struct batadv_algo_ops *bat_algo_ops) +{ + struct batadv_algo_ops *bat_algo_ops_tmp; + int ret; + + bat_algo_ops_tmp = batadv_algo_get(bat_algo_ops->name); + if (bat_algo_ops_tmp) { + pr_info("Trying to register already registered routing algorithm: %s\n", + bat_algo_ops->name); + ret = -EEXIST; + goto out; + } + + /* all algorithms must implement all ops (for now) */ + if (!bat_algo_ops->bat_iface_enable || + !bat_algo_ops->bat_iface_disable || + !bat_algo_ops->bat_iface_update_mac || + !bat_algo_ops->bat_primary_iface_set || + !bat_algo_ops->bat_ogm_schedule || + !bat_algo_ops->bat_ogm_emit) { + pr_info("Routing algo '%s' does not implement required ops\n", + bat_algo_ops->name); + ret = -EINVAL; + goto out; + } + + INIT_HLIST_NODE(&bat_algo_ops->list); + hlist_add_head(&bat_algo_ops->list, &batadv_algo_list); + ret = 0; + +out: + return ret; +} + +int batadv_algo_select(struct batadv_priv *bat_priv, char *name) +{ + struct batadv_algo_ops *bat_algo_ops; + int ret = -EINVAL; + + bat_algo_ops = batadv_algo_get(name); + if (!bat_algo_ops) + goto out; + + bat_priv->bat_algo_ops = bat_algo_ops; + ret = 0; + +out: + return ret; +} + +int batadv_algo_seq_print_text(struct seq_file *seq, void *offset) +{ + struct batadv_algo_ops *bat_algo_ops; + + seq_puts(seq, "Available routing algorithms:\n"); + + hlist_for_each_entry(bat_algo_ops, &batadv_algo_list, list) { + seq_printf(seq, "%s\n", bat_algo_ops->name); + } + + return 0; +} + +/** + * batadv_skb_crc32 - calculate CRC32 of the whole packet and skip bytes in + * the header + * @skb: skb pointing to fragmented socket buffers + * @payload_ptr: Pointer to position inside the head buffer of the skb + * marking the start of the data to be CRC'ed + * + * payload_ptr must always point to an address in the skb head buffer and not to + * a fragment. + */ +__be32 batadv_skb_crc32(struct sk_buff *skb, u8 *payload_ptr) +{ + u32 crc = 0; + unsigned int from; + unsigned int to = skb->len; + struct skb_seq_state st; + const u8 *data; + unsigned int len; + unsigned int consumed = 0; + + from = (unsigned int)(payload_ptr - skb->data); + + skb_prepare_seq_read(skb, from, to, &st); + while ((len = skb_seq_read(consumed, &data, &st)) != 0) { + crc = crc32c(crc, data, len); + consumed += len; + } + skb_abort_seq_read(&st); + + return htonl(crc); +} + +static int batadv_param_set_ra(const char *val, const struct kernel_param *kp) +{ + struct batadv_algo_ops *bat_algo_ops; + char *algo_name = (char *)val; + size_t name_len = strlen(algo_name); + + if (name_len > 0 && algo_name[name_len - 1] == '\n') + algo_name[name_len - 1] = '\0'; + + bat_algo_ops = batadv_algo_get(algo_name); + if (!bat_algo_ops) { + pr_err("Routing algorithm '%s' is not supported\n", algo_name); + return -EINVAL; + } + + return param_set_copystring(algo_name, kp); +} + +static const struct kernel_param_ops batadv_param_ops_ra = { + .set = batadv_param_set_ra, + .get = param_get_string, +}; + +static struct kparam_string batadv_param_string_ra = { + .maxlen = sizeof(batadv_routing_algo), + .string = batadv_routing_algo, +}; + +module_param_cb(routing_algo, &batadv_param_ops_ra, &batadv_param_string_ra, + 0644); +module_init(batadv_init); +module_exit(batadv_exit); + +MODULE_LICENSE("GPL"); + +MODULE_AUTHOR(BATADV_DRIVER_AUTHOR); +MODULE_DESCRIPTION(BATADV_DRIVER_DESC); +MODULE_SUPPORTED_DEVICE(BATADV_DRIVER_DEVICE); +MODULE_VERSION(BATADV_SOURCE_VERSION); diff --git a/net/batman-adv/main.h b/net/batman-adv/main.h new file mode 100644 index 00000000..59a0d6af --- /dev/null +++ b/net/batman-adv/main.h @@ -0,0 +1,312 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_MAIN_H_ +#define _NET_BATMAN_ADV_MAIN_H_ + +#define BATADV_DRIVER_AUTHOR "Marek Lindner <lindner_marek@yahoo.de>, " \ + "Simon Wunderlich <siwu@hrz.tu-chemnitz.de>" +#define BATADV_DRIVER_DESC "B.A.T.M.A.N. advanced" +#define BATADV_DRIVER_DEVICE "batman-adv" + +#ifndef BATADV_SOURCE_VERSION +#define BATADV_SOURCE_VERSION "2013.2.0" +#endif + +/* B.A.T.M.A.N. parameters */ + +#define BATADV_TQ_MAX_VALUE 255 +#define BATADV_JITTER 20 + +/* Time To Live of broadcast messages */ +#define BATADV_TTL 50 + +/* purge originators after time in seconds if no valid packet comes in + * -> TODO: check influence on BATADV_TQ_LOCAL_WINDOW_SIZE + */ +#define BATADV_PURGE_TIMEOUT 200000 /* 200 seconds */ +#define BATADV_TT_LOCAL_TIMEOUT 600000 /* in milliseconds */ +#define BATADV_TT_CLIENT_ROAM_TIMEOUT 600000 /* in milliseconds */ +#define BATADV_TT_CLIENT_TEMP_TIMEOUT 600000 /* in milliseconds */ +#define BATADV_TT_WORK_PERIOD 5000 /* 5 seconds */ +#define BATADV_ORIG_WORK_PERIOD 1000 /* 1 second */ +#define BATADV_DAT_ENTRY_TIMEOUT (5*60000) /* 5 mins in milliseconds */ +/* sliding packet range of received originator messages in sequence numbers + * (should be a multiple of our word size) + */ +#define BATADV_TQ_LOCAL_WINDOW_SIZE 64 +/* milliseconds we have to keep pending tt_req */ +#define BATADV_TT_REQUEST_TIMEOUT 3000 + +#define BATADV_TQ_GLOBAL_WINDOW_SIZE 5 +#define BATADV_TQ_LOCAL_BIDRECT_SEND_MINIMUM 1 +#define BATADV_TQ_LOCAL_BIDRECT_RECV_MINIMUM 1 +#define BATADV_TQ_TOTAL_BIDRECT_LIMIT 1 + +/* number of OGMs sent with the last tt diff */ +#define BATADV_TT_OGM_APPEND_MAX 3 + +/* Time in which a client can roam at most ROAMING_MAX_COUNT times in + * milliseconds + */ +#define BATADV_ROAMING_MAX_TIME 20000 +#define BATADV_ROAMING_MAX_COUNT 5 + +#define BATADV_NO_FLAGS 0 + +#define BATADV_NULL_IFINDEX 0 /* dummy ifindex used to avoid iface checks */ + +#define BATADV_NUM_WORDS BITS_TO_LONGS(BATADV_TQ_LOCAL_WINDOW_SIZE) + +#define BATADV_LOG_BUF_LEN 8192 /* has to be a power of 2 */ + +/* msecs after which an ARP_REQUEST is sent in broadcast as fallback */ +#define ARP_REQ_DELAY 250 +/* numbers of originator to contact for any PUT/GET DHT operation */ +#define BATADV_DAT_CANDIDATES_NUM 3 + +#define BATADV_VIS_INTERVAL 5000 /* 5 seconds */ + +/* how much worse secondary interfaces may be to be considered as bonding + * candidates + */ +#define BATADV_BONDING_TQ_THRESHOLD 50 + +/* should not be bigger than 512 bytes or change the size of + * forw_packet->direct_link_flags + */ +#define BATADV_MAX_AGGREGATION_BYTES 512 +#define BATADV_MAX_AGGREGATION_MS 100 + +#define BATADV_BLA_PERIOD_LENGTH 10000 /* 10 seconds */ +#define BATADV_BLA_BACKBONE_TIMEOUT (BATADV_BLA_PERIOD_LENGTH * 3) +#define BATADV_BLA_CLAIM_TIMEOUT (BATADV_BLA_PERIOD_LENGTH * 10) +#define BATADV_BLA_WAIT_PERIODS 3 + +#define BATADV_DUPLIST_SIZE 16 +#define BATADV_DUPLIST_TIMEOUT 500 /* 500 ms */ +/* don't reset again within 30 seconds */ +#define BATADV_RESET_PROTECTION_MS 30000 +#define BATADV_EXPECTED_SEQNO_RANGE 65536 + +#define BATADV_NC_NODE_TIMEOUT 10000 /* Milliseconds */ + +enum batadv_mesh_state { + BATADV_MESH_INACTIVE, + BATADV_MESH_ACTIVE, + BATADV_MESH_DEACTIVATING, +}; + +#define BATADV_BCAST_QUEUE_LEN 256 +#define BATADV_BATMAN_QUEUE_LEN 256 + +enum batadv_uev_action { + BATADV_UEV_ADD = 0, + BATADV_UEV_DEL, + BATADV_UEV_CHANGE, +}; + +enum batadv_uev_type { + BATADV_UEV_GW = 0, +}; + +#define BATADV_GW_THRESHOLD 50 + +#define BATADV_DAT_CANDIDATE_NOT_FOUND 0 +#define BATADV_DAT_CANDIDATE_ORIG 1 + +/* Debug Messages */ +#ifdef pr_fmt +#undef pr_fmt +#endif +/* Append 'batman-adv: ' before kernel messages */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/* Kernel headers */ + +#include <linux/mutex.h> /* mutex */ +#include <linux/module.h> /* needed by all modules */ +#include <linux/netdevice.h> /* netdevice */ +#include <linux/etherdevice.h> /* ethernet address classification */ +#include <linux/if_ether.h> /* ethernet header */ +#include <linux/poll.h> /* poll_table */ +#include <linux/kthread.h> /* kernel threads */ +#include <linux/pkt_sched.h> /* schedule types */ +#include <linux/workqueue.h> /* workqueue */ +#include <linux/percpu.h> +#include <linux/slab.h> +#include <net/sock.h> /* struct sock */ +#include <net/rtnetlink.h> +#include <linux/jiffies.h> +#include <linux/seq_file.h> +#include "types.h" + +extern char batadv_routing_algo[]; +extern struct list_head batadv_hardif_list; + +extern unsigned char batadv_broadcast_addr[]; +extern struct workqueue_struct *batadv_event_workqueue; + +int batadv_mesh_init(struct net_device *soft_iface); +void batadv_mesh_free(struct net_device *soft_iface); +int batadv_is_my_mac(struct batadv_priv *bat_priv, const uint8_t *addr); +struct batadv_hard_iface * +batadv_seq_print_text_primary_if_get(struct seq_file *seq); +int batadv_batman_skb_recv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *ptype, + struct net_device *orig_dev); +int +batadv_recv_handler_register(uint8_t packet_type, + int (*recv_handler)(struct sk_buff *, + struct batadv_hard_iface *)); +void batadv_recv_handler_unregister(uint8_t packet_type); +int batadv_algo_register(struct batadv_algo_ops *bat_algo_ops); +int batadv_algo_select(struct batadv_priv *bat_priv, char *name); +int batadv_algo_seq_print_text(struct seq_file *seq, void *offset); +__be32 batadv_skb_crc32(struct sk_buff *skb, u8 *payload_ptr); + +/** + * enum batadv_dbg_level - available log levels + * @BATADV_DBG_BATMAN: OGM and TQ computations related messages + * @BATADV_DBG_ROUTES: route added / changed / deleted + * @BATADV_DBG_TT: translation table messages + * @BATADV_DBG_BLA: bridge loop avoidance messages + * @BATADV_DBG_DAT: ARP snooping and DAT related messages + * @BATADV_DBG_NC: network coding related messages + * @BATADV_DBG_ALL: the union of all the above log levels + */ +enum batadv_dbg_level { + BATADV_DBG_BATMAN = BIT(0), + BATADV_DBG_ROUTES = BIT(1), + BATADV_DBG_TT = BIT(2), + BATADV_DBG_BLA = BIT(3), + BATADV_DBG_DAT = BIT(4), + BATADV_DBG_NC = BIT(5), + BATADV_DBG_ALL = 63, +}; + +#ifdef CONFIG_BATMAN_ADV_DEBUG +int batadv_debug_log(struct batadv_priv *bat_priv, const char *fmt, ...) +__printf(2, 3); + +#define batadv_dbg(type, bat_priv, fmt, arg...) \ + do { \ + if (atomic_read(&bat_priv->log_level) & type) \ + batadv_debug_log(bat_priv, fmt, ## arg);\ + } \ + while (0) +#else /* !CONFIG_BATMAN_ADV_DEBUG */ +__printf(3, 4) +static inline void batadv_dbg(int type __always_unused, + struct batadv_priv *bat_priv __always_unused, + const char *fmt __always_unused, ...) +{ +} +#endif + +#define batadv_info(net_dev, fmt, arg...) \ + do { \ + struct net_device *_netdev = (net_dev); \ + struct batadv_priv *_batpriv = netdev_priv(_netdev); \ + batadv_dbg(BATADV_DBG_ALL, _batpriv, fmt, ## arg); \ + pr_info("%s: " fmt, _netdev->name, ## arg); \ + } while (0) +#define batadv_err(net_dev, fmt, arg...) \ + do { \ + struct net_device *_netdev = (net_dev); \ + struct batadv_priv *_batpriv = netdev_priv(_netdev); \ + batadv_dbg(BATADV_DBG_ALL, _batpriv, fmt, ## arg); \ + pr_err("%s: " fmt, _netdev->name, ## arg); \ + } while (0) + +/* returns 1 if they are the same ethernet addr + * + * note: can't use compare_ether_addr() as it requires aligned memory + */ +static inline int batadv_compare_eth(const void *data1, const void *data2) +{ + return (memcmp(data1, data2, ETH_ALEN) == 0 ? 1 : 0); +} + +/** + * has_timed_out - compares current time (jiffies) and timestamp + timeout + * @timestamp: base value to compare with (in jiffies) + * @timeout: added to base value before comparing (in milliseconds) + * + * Returns true if current time is after timestamp + timeout + */ +static inline bool batadv_has_timed_out(unsigned long timestamp, + unsigned int timeout) +{ + return time_is_before_jiffies(timestamp + msecs_to_jiffies(timeout)); +} + +#define batadv_atomic_dec_not_zero(v) atomic_add_unless((v), -1, 0) + +/* Returns the smallest signed integer in two's complement with the sizeof x */ +#define batadv_smallest_signed_int(x) (1u << (7u + 8u * (sizeof(x) - 1u))) + +/* Checks if a sequence number x is a predecessor/successor of y. + * they handle overflows/underflows and can correctly check for a + * predecessor/successor unless the variable sequence number has grown by + * more then 2**(bitwidth(x)-1)-1. + * This means that for a uint8_t with the maximum value 255, it would think: + * - when adding nothing - it is neither a predecessor nor a successor + * - before adding more than 127 to the starting value - it is a predecessor, + * - when adding 128 - it is neither a predecessor nor a successor, + * - after adding more than 127 to the starting value - it is a successor + */ +#define batadv_seq_before(x, y) ({typeof(x) _d1 = (x); \ + typeof(y) _d2 = (y); \ + typeof(x) _dummy = (_d1 - _d2); \ + (void) (&_d1 == &_d2); \ + _dummy > batadv_smallest_signed_int(_dummy); }) +#define batadv_seq_after(x, y) batadv_seq_before(y, x) + +/* Stop preemption on local cpu while incrementing the counter */ +static inline void batadv_add_counter(struct batadv_priv *bat_priv, size_t idx, + size_t count) +{ + this_cpu_add(bat_priv->bat_counters[idx], count); +} + +#define batadv_inc_counter(b, i) batadv_add_counter(b, i, 1) + +/* Sum and return the cpu-local counters for index 'idx' */ +static inline uint64_t batadv_sum_counter(struct batadv_priv *bat_priv, + size_t idx) +{ + uint64_t *counters, sum = 0; + int cpu; + + for_each_possible_cpu(cpu) { + counters = per_cpu_ptr(bat_priv->bat_counters, cpu); + sum += counters[idx]; + } + + return sum; +} + +/* Define a macro to reach the control buffer of the skb. The members of the + * control buffer are defined in struct batadv_skb_cb in types.h. + * The macro is inspired by the similar macro TCP_SKB_CB() in tcp.h. + */ +#define BATADV_SKB_CB(__skb) ((struct batadv_skb_cb *)&((__skb)->cb[0])) + +#endif /* _NET_BATMAN_ADV_MAIN_H_ */ diff --git a/net/batman-adv/network-coding.c b/net/batman-adv/network-coding.c new file mode 100644 index 00000000..f97aeee2 --- /dev/null +++ b/net/batman-adv/network-coding.c @@ -0,0 +1,1834 @@ +/* Copyright (C) 2012-2013 B.A.T.M.A.N. contributors: + * + * Martin Hundebøll, Jeppe Ledet-Pedersen + * + * 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 <linux/debugfs.h> + +#include "main.h" +#include "hash.h" +#include "network-coding.h" +#include "send.h" +#include "originator.h" +#include "hard-interface.h" +#include "routing.h" + +static struct lock_class_key batadv_nc_coding_hash_lock_class_key; +static struct lock_class_key batadv_nc_decoding_hash_lock_class_key; + +static void batadv_nc_worker(struct work_struct *work); +static int batadv_nc_recv_coded_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); + +/** + * batadv_nc_init - one-time initialization for network coding + */ +int __init batadv_nc_init(void) +{ + int ret; + + /* Register our packet type */ + ret = batadv_recv_handler_register(BATADV_CODED, + batadv_nc_recv_coded_packet); + + return ret; +} + +/** + * batadv_nc_start_timer - initialise the nc periodic worker + * @bat_priv: the bat priv with all the soft interface information + */ +static void batadv_nc_start_timer(struct batadv_priv *bat_priv) +{ + queue_delayed_work(batadv_event_workqueue, &bat_priv->nc.work, + msecs_to_jiffies(10)); +} + +/** + * batadv_nc_mesh_init - initialise coding hash table and start house keeping + * @bat_priv: the bat priv with all the soft interface information + */ +int batadv_nc_mesh_init(struct batadv_priv *bat_priv) +{ + bat_priv->nc.timestamp_fwd_flush = jiffies; + bat_priv->nc.timestamp_sniffed_purge = jiffies; + + if (bat_priv->nc.coding_hash || bat_priv->nc.decoding_hash) + return 0; + + bat_priv->nc.coding_hash = batadv_hash_new(128); + if (!bat_priv->nc.coding_hash) + goto err; + + batadv_hash_set_lock_class(bat_priv->nc.coding_hash, + &batadv_nc_coding_hash_lock_class_key); + + bat_priv->nc.decoding_hash = batadv_hash_new(128); + if (!bat_priv->nc.decoding_hash) + goto err; + + batadv_hash_set_lock_class(bat_priv->nc.coding_hash, + &batadv_nc_decoding_hash_lock_class_key); + + INIT_DELAYED_WORK(&bat_priv->nc.work, batadv_nc_worker); + batadv_nc_start_timer(bat_priv); + + return 0; + +err: + return -ENOMEM; +} + +/** + * batadv_nc_init_bat_priv - initialise the nc specific bat_priv variables + * @bat_priv: the bat priv with all the soft interface information + */ +void batadv_nc_init_bat_priv(struct batadv_priv *bat_priv) +{ + atomic_set(&bat_priv->network_coding, 1); + bat_priv->nc.min_tq = 200; + bat_priv->nc.max_fwd_delay = 10; + bat_priv->nc.max_buffer_time = 200; +} + +/** + * batadv_nc_init_orig - initialise the nc fields of an orig_node + * @orig_node: the orig_node which is going to be initialised + */ +void batadv_nc_init_orig(struct batadv_orig_node *orig_node) +{ + INIT_LIST_HEAD(&orig_node->in_coding_list); + INIT_LIST_HEAD(&orig_node->out_coding_list); + spin_lock_init(&orig_node->in_coding_list_lock); + spin_lock_init(&orig_node->out_coding_list_lock); +} + +/** + * batadv_nc_node_free_rcu - rcu callback to free an nc node and remove + * its refcount on the orig_node + * @rcu: rcu pointer of the nc node + */ +static void batadv_nc_node_free_rcu(struct rcu_head *rcu) +{ + struct batadv_nc_node *nc_node; + + nc_node = container_of(rcu, struct batadv_nc_node, rcu); + batadv_orig_node_free_ref(nc_node->orig_node); + kfree(nc_node); +} + +/** + * batadv_nc_node_free_ref - decrements the nc node refcounter and possibly + * frees it + * @nc_node: the nc node to free + */ +static void batadv_nc_node_free_ref(struct batadv_nc_node *nc_node) +{ + if (atomic_dec_and_test(&nc_node->refcount)) + call_rcu(&nc_node->rcu, batadv_nc_node_free_rcu); +} + +/** + * batadv_nc_path_free_ref - decrements the nc path refcounter and possibly + * frees it + * @nc_path: the nc node to free + */ +static void batadv_nc_path_free_ref(struct batadv_nc_path *nc_path) +{ + if (atomic_dec_and_test(&nc_path->refcount)) + kfree_rcu(nc_path, rcu); +} + +/** + * batadv_nc_packet_free - frees nc packet + * @nc_packet: the nc packet to free + */ +static void batadv_nc_packet_free(struct batadv_nc_packet *nc_packet) +{ + if (nc_packet->skb) + kfree_skb(nc_packet->skb); + + batadv_nc_path_free_ref(nc_packet->nc_path); + kfree(nc_packet); +} + +/** + * batadv_nc_to_purge_nc_node - checks whether an nc node has to be purged + * @bat_priv: the bat priv with all the soft interface information + * @nc_node: the nc node to check + * + * Returns true if the entry has to be purged now, false otherwise + */ +static bool batadv_nc_to_purge_nc_node(struct batadv_priv *bat_priv, + struct batadv_nc_node *nc_node) +{ + if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) + return true; + + return batadv_has_timed_out(nc_node->last_seen, BATADV_NC_NODE_TIMEOUT); +} + +/** + * batadv_nc_to_purge_nc_path_coding - checks whether an nc path has timed out + * @bat_priv: the bat priv with all the soft interface information + * @nc_path: the nc path to check + * + * Returns true if the entry has to be purged now, false otherwise + */ +static bool batadv_nc_to_purge_nc_path_coding(struct batadv_priv *bat_priv, + struct batadv_nc_path *nc_path) +{ + if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) + return true; + + /* purge the path when no packets has been added for 10 times the + * max_fwd_delay time + */ + return batadv_has_timed_out(nc_path->last_valid, + bat_priv->nc.max_fwd_delay * 10); +} + +/** + * batadv_nc_to_purge_nc_path_decoding - checks whether an nc path has timed out + * @bat_priv: the bat priv with all the soft interface information + * @nc_path: the nc path to check + * + * Returns true if the entry has to be purged now, false otherwise + */ +static bool batadv_nc_to_purge_nc_path_decoding(struct batadv_priv *bat_priv, + struct batadv_nc_path *nc_path) +{ + if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) + return true; + + /* purge the path when no packets has been added for 10 times the + * max_buffer time + */ + return batadv_has_timed_out(nc_path->last_valid, + bat_priv->nc.max_buffer_time*10); +} + +/** + * batadv_nc_purge_orig_nc_nodes - go through list of nc nodes and purge stale + * entries + * @bat_priv: the bat priv with all the soft interface information + * @list: list of nc nodes + * @lock: nc node list lock + * @to_purge: function in charge to decide whether an entry has to be purged or + * not. This function takes the nc node as argument and has to return + * a boolean value: true if the entry has to be deleted, false + * otherwise + */ +static void +batadv_nc_purge_orig_nc_nodes(struct batadv_priv *bat_priv, + struct list_head *list, + spinlock_t *lock, + bool (*to_purge)(struct batadv_priv *, + struct batadv_nc_node *)) +{ + struct batadv_nc_node *nc_node, *nc_node_tmp; + + /* For each nc_node in list */ + spin_lock_bh(lock); + list_for_each_entry_safe(nc_node, nc_node_tmp, list, list) { + /* if an helper function has been passed as parameter, + * ask it if the entry has to be purged or not + */ + if (to_purge && !to_purge(bat_priv, nc_node)) + continue; + + batadv_dbg(BATADV_DBG_NC, bat_priv, + "Removing nc_node %pM -> %pM\n", + nc_node->addr, nc_node->orig_node->orig); + list_del_rcu(&nc_node->list); + batadv_nc_node_free_ref(nc_node); + } + spin_unlock_bh(lock); +} + +/** + * batadv_nc_purge_orig - purges all nc node data attached of the given + * originator + * @bat_priv: the bat priv with all the soft interface information + * @orig_node: orig_node with the nc node entries to be purged + * @to_purge: function in charge to decide whether an entry has to be purged or + * not. This function takes the nc node as argument and has to return + * a boolean value: true is the entry has to be deleted, false + * otherwise + */ +void batadv_nc_purge_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + bool (*to_purge)(struct batadv_priv *, + struct batadv_nc_node *)) +{ + /* Check ingoing nc_node's of this orig_node */ + batadv_nc_purge_orig_nc_nodes(bat_priv, &orig_node->in_coding_list, + &orig_node->in_coding_list_lock, + to_purge); + + /* Check outgoing nc_node's of this orig_node */ + batadv_nc_purge_orig_nc_nodes(bat_priv, &orig_node->out_coding_list, + &orig_node->out_coding_list_lock, + to_purge); +} + +/** + * batadv_nc_purge_orig_hash - traverse entire originator hash to check if they + * have timed out nc nodes + * @bat_priv: the bat priv with all the soft interface information + */ +static void batadv_nc_purge_orig_hash(struct batadv_priv *bat_priv) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + uint32_t i; + + if (!hash) + return; + + /* For each orig_node */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) + batadv_nc_purge_orig(bat_priv, orig_node, + batadv_nc_to_purge_nc_node); + rcu_read_unlock(); + } +} + +/** + * batadv_nc_purge_paths - traverse all nc paths part of the hash and remove + * unused ones + * @bat_priv: the bat priv with all the soft interface information + * @hash: hash table containing the nc paths to check + * @to_purge: function in charge to decide whether an entry has to be purged or + * not. This function takes the nc node as argument and has to return + * a boolean value: true is the entry has to be deleted, false + * otherwise + */ +static void batadv_nc_purge_paths(struct batadv_priv *bat_priv, + struct batadv_hashtable *hash, + bool (*to_purge)(struct batadv_priv *, + struct batadv_nc_path *)) +{ + struct hlist_head *head; + struct hlist_node *node_tmp; + struct batadv_nc_path *nc_path; + spinlock_t *lock; /* Protects lists in hash */ + uint32_t i; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + lock = &hash->list_locks[i]; + + /* For each nc_path in this bin */ + spin_lock_bh(lock); + hlist_for_each_entry_safe(nc_path, node_tmp, head, hash_entry) { + /* if an helper function has been passed as parameter, + * ask it if the entry has to be purged or not + */ + if (to_purge && !to_purge(bat_priv, nc_path)) + continue; + + /* purging an non-empty nc_path should never happen, but + * is observed under high CPU load. Delay the purging + * until next iteration to allow the packet_list to be + * emptied first. + */ + if (!unlikely(list_empty(&nc_path->packet_list))) { + net_ratelimited_function(printk, + KERN_WARNING + "Skipping free of non-empty nc_path (%pM -> %pM)!\n", + nc_path->prev_hop, + nc_path->next_hop); + continue; + } + + /* nc_path is unused, so remove it */ + batadv_dbg(BATADV_DBG_NC, bat_priv, + "Remove nc_path %pM -> %pM\n", + nc_path->prev_hop, nc_path->next_hop); + hlist_del_rcu(&nc_path->hash_entry); + batadv_nc_path_free_ref(nc_path); + } + spin_unlock_bh(lock); + } +} + +/** + * batadv_nc_hash_key_gen - computes the nc_path hash key + * @key: buffer to hold the final hash key + * @src: source ethernet mac address going into the hash key + * @dst: destination ethernet mac address going into the hash key + */ +static void batadv_nc_hash_key_gen(struct batadv_nc_path *key, const char *src, + const char *dst) +{ + memcpy(key->prev_hop, src, sizeof(key->prev_hop)); + memcpy(key->next_hop, dst, sizeof(key->next_hop)); +} + +/** + * batadv_nc_hash_choose - compute the hash value for an nc path + * @data: data to hash + * @size: size of the hash table + * + * Returns the selected index in the hash table for the given data. + */ +static uint32_t batadv_nc_hash_choose(const void *data, uint32_t size) +{ + const struct batadv_nc_path *nc_path = data; + uint32_t hash = 0; + + hash = batadv_hash_bytes(hash, &nc_path->prev_hop, + sizeof(nc_path->prev_hop)); + hash = batadv_hash_bytes(hash, &nc_path->next_hop, + sizeof(nc_path->next_hop)); + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash % size; +} + +/** + * batadv_nc_hash_compare - comparing function used in the network coding hash + * tables + * @node: node in the local table + * @data2: second object to compare the node to + * + * Returns 1 if the two entry are the same, 0 otherwise + */ +static int batadv_nc_hash_compare(const struct hlist_node *node, + const void *data2) +{ + const struct batadv_nc_path *nc_path1, *nc_path2; + + nc_path1 = container_of(node, struct batadv_nc_path, hash_entry); + nc_path2 = data2; + + /* Return 1 if the two keys are identical */ + if (memcmp(nc_path1->prev_hop, nc_path2->prev_hop, + sizeof(nc_path1->prev_hop)) != 0) + return 0; + + if (memcmp(nc_path1->next_hop, nc_path2->next_hop, + sizeof(nc_path1->next_hop)) != 0) + return 0; + + return 1; +} + +/** + * batadv_nc_hash_find - search for an existing nc path and return it + * @hash: hash table containing the nc path + * @data: search key + * + * Returns the nc_path if found, NULL otherwise. + */ +static struct batadv_nc_path * +batadv_nc_hash_find(struct batadv_hashtable *hash, + void *data) +{ + struct hlist_head *head; + struct batadv_nc_path *nc_path, *nc_path_tmp = NULL; + int index; + + if (!hash) + return NULL; + + index = batadv_nc_hash_choose(data, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(nc_path, head, hash_entry) { + if (!batadv_nc_hash_compare(&nc_path->hash_entry, data)) + continue; + + if (!atomic_inc_not_zero(&nc_path->refcount)) + continue; + + nc_path_tmp = nc_path; + break; + } + rcu_read_unlock(); + + return nc_path_tmp; +} + +/** + * batadv_nc_send_packet - send non-coded packet and free nc_packet struct + * @nc_packet: the nc packet to send + */ +static void batadv_nc_send_packet(struct batadv_nc_packet *nc_packet) +{ + batadv_send_skb_packet(nc_packet->skb, + nc_packet->neigh_node->if_incoming, + nc_packet->nc_path->next_hop); + nc_packet->skb = NULL; + batadv_nc_packet_free(nc_packet); +} + +/** + * batadv_nc_sniffed_purge - Checks timestamp of given sniffed nc_packet. + * @bat_priv: the bat priv with all the soft interface information + * @nc_path: the nc path the packet belongs to + * @nc_packet: the nc packet to be checked + * + * Checks whether the given sniffed (overheard) nc_packet has hit its buffering + * timeout. If so, the packet is no longer kept and the entry deleted from the + * queue. Has to be called with the appropriate locks. + * + * Returns false as soon as the entry in the fifo queue has not been timed out + * yet and true otherwise. + */ +static bool batadv_nc_sniffed_purge(struct batadv_priv *bat_priv, + struct batadv_nc_path *nc_path, + struct batadv_nc_packet *nc_packet) +{ + unsigned long timeout = bat_priv->nc.max_buffer_time; + bool res = false; + + /* Packets are added to tail, so the remaining packets did not time + * out and we can stop processing the current queue + */ + if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_ACTIVE && + !batadv_has_timed_out(nc_packet->timestamp, timeout)) + goto out; + + /* purge nc packet */ + list_del(&nc_packet->list); + batadv_nc_packet_free(nc_packet); + + res = true; + +out: + return res; +} + +/** + * batadv_nc_fwd_flush - Checks the timestamp of the given nc packet. + * @bat_priv: the bat priv with all the soft interface information + * @nc_path: the nc path the packet belongs to + * @nc_packet: the nc packet to be checked + * + * Checks whether the given nc packet has hit its forward timeout. If so, the + * packet is no longer delayed, immediately sent and the entry deleted from the + * queue. Has to be called with the appropriate locks. + * + * Returns false as soon as the entry in the fifo queue has not been timed out + * yet and true otherwise. + */ +static bool batadv_nc_fwd_flush(struct batadv_priv *bat_priv, + struct batadv_nc_path *nc_path, + struct batadv_nc_packet *nc_packet) +{ + unsigned long timeout = bat_priv->nc.max_fwd_delay; + + /* Packets are added to tail, so the remaining packets did not time + * out and we can stop processing the current queue + */ + if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_ACTIVE && + !batadv_has_timed_out(nc_packet->timestamp, timeout)) + return false; + + /* Send packet */ + batadv_inc_counter(bat_priv, BATADV_CNT_FORWARD); + batadv_add_counter(bat_priv, BATADV_CNT_FORWARD_BYTES, + nc_packet->skb->len + ETH_HLEN); + list_del(&nc_packet->list); + batadv_nc_send_packet(nc_packet); + + return true; +} + +/** + * batadv_nc_process_nc_paths - traverse given nc packet pool and free timed out + * nc packets + * @bat_priv: the bat priv with all the soft interface information + * @hash: to be processed hash table + * @process_fn: Function called to process given nc packet. Should return true + * to encourage this function to proceed with the next packet. + * Otherwise the rest of the current queue is skipped. + */ +static void +batadv_nc_process_nc_paths(struct batadv_priv *bat_priv, + struct batadv_hashtable *hash, + bool (*process_fn)(struct batadv_priv *, + struct batadv_nc_path *, + struct batadv_nc_packet *)) +{ + struct hlist_head *head; + struct batadv_nc_packet *nc_packet, *nc_packet_tmp; + struct batadv_nc_path *nc_path; + bool ret; + int i; + + if (!hash) + return; + + /* Loop hash table bins */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + /* Loop coding paths */ + rcu_read_lock(); + hlist_for_each_entry_rcu(nc_path, head, hash_entry) { + /* Loop packets */ + spin_lock_bh(&nc_path->packet_list_lock); + list_for_each_entry_safe(nc_packet, nc_packet_tmp, + &nc_path->packet_list, list) { + ret = process_fn(bat_priv, nc_path, nc_packet); + if (!ret) + break; + } + spin_unlock_bh(&nc_path->packet_list_lock); + } + rcu_read_unlock(); + } +} + +/** + * batadv_nc_worker - periodic task for house keeping related to network coding + * @work: kernel work struct + */ +static void batadv_nc_worker(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct batadv_priv_nc *priv_nc; + struct batadv_priv *bat_priv; + unsigned long timeout; + + delayed_work = container_of(work, struct delayed_work, work); + priv_nc = container_of(delayed_work, struct batadv_priv_nc, work); + bat_priv = container_of(priv_nc, struct batadv_priv, nc); + + batadv_nc_purge_orig_hash(bat_priv); + batadv_nc_purge_paths(bat_priv, bat_priv->nc.coding_hash, + batadv_nc_to_purge_nc_path_coding); + batadv_nc_purge_paths(bat_priv, bat_priv->nc.decoding_hash, + batadv_nc_to_purge_nc_path_decoding); + + timeout = bat_priv->nc.max_fwd_delay; + + if (batadv_has_timed_out(bat_priv->nc.timestamp_fwd_flush, timeout)) { + batadv_nc_process_nc_paths(bat_priv, bat_priv->nc.coding_hash, + batadv_nc_fwd_flush); + bat_priv->nc.timestamp_fwd_flush = jiffies; + } + + if (batadv_has_timed_out(bat_priv->nc.timestamp_sniffed_purge, + bat_priv->nc.max_buffer_time)) { + batadv_nc_process_nc_paths(bat_priv, bat_priv->nc.decoding_hash, + batadv_nc_sniffed_purge); + bat_priv->nc.timestamp_sniffed_purge = jiffies; + } + + /* Schedule a new check */ + batadv_nc_start_timer(bat_priv); +} + +/** + * batadv_can_nc_with_orig - checks whether the given orig node is suitable for + * coding or not + * @bat_priv: the bat priv with all the soft interface information + * @orig_node: neighboring orig node which may be used as nc candidate + * @ogm_packet: incoming ogm packet also used for the checks + * + * Returns true if: + * 1) The OGM must have the most recent sequence number. + * 2) The TTL must be decremented by one and only one. + * 3) The OGM must be received from the first hop from orig_node. + * 4) The TQ value of the OGM must be above bat_priv->nc.min_tq. + */ +static bool batadv_can_nc_with_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_ogm_packet *ogm_packet) +{ + if (orig_node->last_real_seqno != ntohl(ogm_packet->seqno)) + return false; + if (orig_node->last_ttl != ogm_packet->header.ttl + 1) + return false; + if (!batadv_compare_eth(ogm_packet->orig, ogm_packet->prev_sender)) + return false; + if (ogm_packet->tq < bat_priv->nc.min_tq) + return false; + + return true; +} + +/** + * batadv_nc_find_nc_node - search for an existing nc node and return it + * @orig_node: orig node originating the ogm packet + * @orig_neigh_node: neighboring orig node from which we received the ogm packet + * (can be equal to orig_node) + * @in_coding: traverse incoming or outgoing network coding list + * + * Returns the nc_node if found, NULL otherwise. + */ +static struct batadv_nc_node +*batadv_nc_find_nc_node(struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + bool in_coding) +{ + struct batadv_nc_node *nc_node, *nc_node_out = NULL; + struct list_head *list; + + if (in_coding) + list = &orig_neigh_node->in_coding_list; + else + list = &orig_neigh_node->out_coding_list; + + /* Traverse list of nc_nodes to orig_node */ + rcu_read_lock(); + list_for_each_entry_rcu(nc_node, list, list) { + if (!batadv_compare_eth(nc_node->addr, orig_node->orig)) + continue; + + if (!atomic_inc_not_zero(&nc_node->refcount)) + continue; + + /* Found a match */ + nc_node_out = nc_node; + break; + } + rcu_read_unlock(); + + return nc_node_out; +} + +/** + * batadv_nc_get_nc_node - retrieves an nc node or creates the entry if it was + * not found + * @bat_priv: the bat priv with all the soft interface information + * @orig_node: orig node originating the ogm packet + * @orig_neigh_node: neighboring orig node from which we received the ogm packet + * (can be equal to orig_node) + * @in_coding: traverse incoming or outgoing network coding list + * + * Returns the nc_node if found or created, NULL in case of an error. + */ +static struct batadv_nc_node +*batadv_nc_get_nc_node(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + bool in_coding) +{ + struct batadv_nc_node *nc_node; + spinlock_t *lock; /* Used to lock list selected by "int in_coding" */ + struct list_head *list; + + /* Check if nc_node is already added */ + nc_node = batadv_nc_find_nc_node(orig_node, orig_neigh_node, in_coding); + + /* Node found */ + if (nc_node) + return nc_node; + + nc_node = kzalloc(sizeof(*nc_node), GFP_ATOMIC); + if (!nc_node) + return NULL; + + if (!atomic_inc_not_zero(&orig_neigh_node->refcount)) + goto free; + + /* Initialize nc_node */ + INIT_LIST_HEAD(&nc_node->list); + memcpy(nc_node->addr, orig_node->orig, ETH_ALEN); + nc_node->orig_node = orig_neigh_node; + atomic_set(&nc_node->refcount, 2); + + /* Select ingoing or outgoing coding node */ + if (in_coding) { + lock = &orig_neigh_node->in_coding_list_lock; + list = &orig_neigh_node->in_coding_list; + } else { + lock = &orig_neigh_node->out_coding_list_lock; + list = &orig_neigh_node->out_coding_list; + } + + batadv_dbg(BATADV_DBG_NC, bat_priv, "Adding nc_node %pM -> %pM\n", + nc_node->addr, nc_node->orig_node->orig); + + /* Add nc_node to orig_node */ + spin_lock_bh(lock); + list_add_tail_rcu(&nc_node->list, list); + spin_unlock_bh(lock); + + return nc_node; + +free: + kfree(nc_node); + return NULL; +} + +/** + * batadv_nc_update_nc_node - updates stored incoming and outgoing nc node structs + * (best called on incoming OGMs) + * @bat_priv: the bat priv with all the soft interface information + * @orig_node: orig node originating the ogm packet + * @orig_neigh_node: neighboring orig node from which we received the ogm packet + * (can be equal to orig_node) + * @ogm_packet: incoming ogm packet + * @is_single_hop_neigh: orig_node is a single hop neighbor + */ +void batadv_nc_update_nc_node(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + struct batadv_ogm_packet *ogm_packet, + int is_single_hop_neigh) +{ + struct batadv_nc_node *in_nc_node = NULL, *out_nc_node = NULL; + + /* Check if network coding is enabled */ + if (!atomic_read(&bat_priv->network_coding)) + goto out; + + /* accept ogms from 'good' neighbors and single hop neighbors */ + if (!batadv_can_nc_with_orig(bat_priv, orig_node, ogm_packet) && + !is_single_hop_neigh) + goto out; + + /* Add orig_node as in_nc_node on hop */ + in_nc_node = batadv_nc_get_nc_node(bat_priv, orig_node, + orig_neigh_node, true); + if (!in_nc_node) + goto out; + + in_nc_node->last_seen = jiffies; + + /* Add hop as out_nc_node on orig_node */ + out_nc_node = batadv_nc_get_nc_node(bat_priv, orig_neigh_node, + orig_node, false); + if (!out_nc_node) + goto out; + + out_nc_node->last_seen = jiffies; + +out: + if (in_nc_node) + batadv_nc_node_free_ref(in_nc_node); + if (out_nc_node) + batadv_nc_node_free_ref(out_nc_node); +} + +/** + * batadv_nc_get_path - get existing nc_path or allocate a new one + * @bat_priv: the bat priv with all the soft interface information + * @hash: hash table containing the nc path + * @src: ethernet source address - first half of the nc path search key + * @dst: ethernet destination address - second half of the nc path search key + * + * Returns pointer to nc_path if the path was found or created, returns NULL + * on error. + */ +static struct batadv_nc_path *batadv_nc_get_path(struct batadv_priv *bat_priv, + struct batadv_hashtable *hash, + uint8_t *src, + uint8_t *dst) +{ + int hash_added; + struct batadv_nc_path *nc_path, nc_path_key; + + batadv_nc_hash_key_gen(&nc_path_key, src, dst); + + /* Search for existing nc_path */ + nc_path = batadv_nc_hash_find(hash, (void *)&nc_path_key); + + if (nc_path) { + /* Set timestamp to delay removal of nc_path */ + nc_path->last_valid = jiffies; + return nc_path; + } + + /* No existing nc_path was found; create a new */ + nc_path = kzalloc(sizeof(*nc_path), GFP_ATOMIC); + + if (!nc_path) + return NULL; + + /* Initialize nc_path */ + INIT_LIST_HEAD(&nc_path->packet_list); + spin_lock_init(&nc_path->packet_list_lock); + atomic_set(&nc_path->refcount, 2); + nc_path->last_valid = jiffies; + memcpy(nc_path->next_hop, dst, ETH_ALEN); + memcpy(nc_path->prev_hop, src, ETH_ALEN); + + batadv_dbg(BATADV_DBG_NC, bat_priv, "Adding nc_path %pM -> %pM\n", + nc_path->prev_hop, + nc_path->next_hop); + + /* Add nc_path to hash table */ + hash_added = batadv_hash_add(hash, batadv_nc_hash_compare, + batadv_nc_hash_choose, &nc_path_key, + &nc_path->hash_entry); + + if (hash_added < 0) { + kfree(nc_path); + return NULL; + } + + return nc_path; +} + +/** + * batadv_nc_random_weight_tq - scale the receivers TQ-value to avoid unfair + * selection of a receiver with slightly lower TQ than the other + * @tq: to be weighted tq value + */ +static uint8_t batadv_nc_random_weight_tq(uint8_t tq) +{ + uint8_t rand_val, rand_tq; + + get_random_bytes(&rand_val, sizeof(rand_val)); + + /* randomize the estimated packet loss (max TQ - estimated TQ) */ + rand_tq = rand_val * (BATADV_TQ_MAX_VALUE - tq); + + /* normalize the randomized packet loss */ + rand_tq /= BATADV_TQ_MAX_VALUE; + + /* convert to (randomized) estimated tq again */ + return BATADV_TQ_MAX_VALUE - rand_tq; +} + +/** + * batadv_nc_memxor - XOR destination with source + * @dst: byte array to XOR into + * @src: byte array to XOR from + * @len: length of destination array + */ +static void batadv_nc_memxor(char *dst, const char *src, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; ++i) + dst[i] ^= src[i]; +} + +/** + * batadv_nc_code_packets - code a received unicast_packet with an nc packet + * into a coded_packet and send it + * @bat_priv: the bat priv with all the soft interface information + * @skb: data skb to forward + * @ethhdr: pointer to the ethernet header inside the skb + * @nc_packet: structure containing the packet to the skb can be coded with + * @neigh_node: next hop to forward packet to + * + * Returns true if both packets are consumed, false otherwise. + */ +static bool batadv_nc_code_packets(struct batadv_priv *bat_priv, + struct sk_buff *skb, + struct ethhdr *ethhdr, + struct batadv_nc_packet *nc_packet, + struct batadv_neigh_node *neigh_node) +{ + uint8_t tq_weighted_neigh, tq_weighted_coding; + struct sk_buff *skb_dest, *skb_src; + struct batadv_unicast_packet *packet1; + struct batadv_unicast_packet *packet2; + struct batadv_coded_packet *coded_packet; + struct batadv_neigh_node *neigh_tmp, *router_neigh; + struct batadv_neigh_node *router_coding = NULL; + uint8_t *first_source, *first_dest, *second_source, *second_dest; + __be32 packet_id1, packet_id2; + size_t count; + bool res = false; + int coding_len; + int unicast_size = sizeof(*packet1); + int coded_size = sizeof(*coded_packet); + int header_add = coded_size - unicast_size; + + router_neigh = batadv_orig_node_get_router(neigh_node->orig_node); + if (!router_neigh) + goto out; + + neigh_tmp = nc_packet->neigh_node; + router_coding = batadv_orig_node_get_router(neigh_tmp->orig_node); + if (!router_coding) + goto out; + + tq_weighted_neigh = batadv_nc_random_weight_tq(router_neigh->tq_avg); + tq_weighted_coding = batadv_nc_random_weight_tq(router_coding->tq_avg); + + /* Select one destination for the MAC-header dst-field based on + * weighted TQ-values. + */ + if (tq_weighted_neigh >= tq_weighted_coding) { + /* Destination from nc_packet is selected for MAC-header */ + first_dest = nc_packet->nc_path->next_hop; + first_source = nc_packet->nc_path->prev_hop; + second_dest = neigh_node->addr; + second_source = ethhdr->h_source; + packet1 = (struct batadv_unicast_packet *)nc_packet->skb->data; + packet2 = (struct batadv_unicast_packet *)skb->data; + packet_id1 = nc_packet->packet_id; + packet_id2 = batadv_skb_crc32(skb, + skb->data + sizeof(*packet2)); + } else { + /* Destination for skb is selected for MAC-header */ + first_dest = neigh_node->addr; + first_source = ethhdr->h_source; + second_dest = nc_packet->nc_path->next_hop; + second_source = nc_packet->nc_path->prev_hop; + packet1 = (struct batadv_unicast_packet *)skb->data; + packet2 = (struct batadv_unicast_packet *)nc_packet->skb->data; + packet_id1 = batadv_skb_crc32(skb, + skb->data + sizeof(*packet1)); + packet_id2 = nc_packet->packet_id; + } + + /* Instead of zero padding the smallest data buffer, we + * code into the largest. + */ + if (skb->len <= nc_packet->skb->len) { + skb_dest = nc_packet->skb; + skb_src = skb; + } else { + skb_dest = skb; + skb_src = nc_packet->skb; + } + + /* coding_len is used when decoding the packet shorter packet */ + coding_len = skb_src->len - unicast_size; + + if (skb_linearize(skb_dest) < 0 || skb_linearize(skb_src) < 0) + goto out; + + skb_push(skb_dest, header_add); + + coded_packet = (struct batadv_coded_packet *)skb_dest->data; + skb_reset_mac_header(skb_dest); + + coded_packet->header.packet_type = BATADV_CODED; + coded_packet->header.version = BATADV_COMPAT_VERSION; + coded_packet->header.ttl = packet1->header.ttl; + + /* Info about first unicast packet */ + memcpy(coded_packet->first_source, first_source, ETH_ALEN); + memcpy(coded_packet->first_orig_dest, packet1->dest, ETH_ALEN); + coded_packet->first_crc = packet_id1; + coded_packet->first_ttvn = packet1->ttvn; + + /* Info about second unicast packet */ + memcpy(coded_packet->second_dest, second_dest, ETH_ALEN); + memcpy(coded_packet->second_source, second_source, ETH_ALEN); + memcpy(coded_packet->second_orig_dest, packet2->dest, ETH_ALEN); + coded_packet->second_crc = packet_id2; + coded_packet->second_ttl = packet2->header.ttl; + coded_packet->second_ttvn = packet2->ttvn; + coded_packet->coded_len = htons(coding_len); + + /* This is where the magic happens: Code skb_src into skb_dest */ + batadv_nc_memxor(skb_dest->data + coded_size, + skb_src->data + unicast_size, coding_len); + + /* Update counters accordingly */ + if (BATADV_SKB_CB(skb_src)->decoded && + BATADV_SKB_CB(skb_dest)->decoded) { + /* Both packets are recoded */ + count = skb_src->len + ETH_HLEN; + count += skb_dest->len + ETH_HLEN; + batadv_add_counter(bat_priv, BATADV_CNT_NC_RECODE, 2); + batadv_add_counter(bat_priv, BATADV_CNT_NC_RECODE_BYTES, count); + } else if (!BATADV_SKB_CB(skb_src)->decoded && + !BATADV_SKB_CB(skb_dest)->decoded) { + /* Both packets are newly coded */ + count = skb_src->len + ETH_HLEN; + count += skb_dest->len + ETH_HLEN; + batadv_add_counter(bat_priv, BATADV_CNT_NC_CODE, 2); + batadv_add_counter(bat_priv, BATADV_CNT_NC_CODE_BYTES, count); + } else if (BATADV_SKB_CB(skb_src)->decoded && + !BATADV_SKB_CB(skb_dest)->decoded) { + /* skb_src recoded and skb_dest is newly coded */ + batadv_inc_counter(bat_priv, BATADV_CNT_NC_RECODE); + batadv_add_counter(bat_priv, BATADV_CNT_NC_RECODE_BYTES, + skb_src->len + ETH_HLEN); + batadv_inc_counter(bat_priv, BATADV_CNT_NC_CODE); + batadv_add_counter(bat_priv, BATADV_CNT_NC_CODE_BYTES, + skb_dest->len + ETH_HLEN); + } else if (!BATADV_SKB_CB(skb_src)->decoded && + BATADV_SKB_CB(skb_dest)->decoded) { + /* skb_src is newly coded and skb_dest is recoded */ + batadv_inc_counter(bat_priv, BATADV_CNT_NC_CODE); + batadv_add_counter(bat_priv, BATADV_CNT_NC_CODE_BYTES, + skb_src->len + ETH_HLEN); + batadv_inc_counter(bat_priv, BATADV_CNT_NC_RECODE); + batadv_add_counter(bat_priv, BATADV_CNT_NC_RECODE_BYTES, + skb_dest->len + ETH_HLEN); + } + + /* skb_src is now coded into skb_dest, so free it */ + kfree_skb(skb_src); + + /* avoid duplicate free of skb from nc_packet */ + nc_packet->skb = NULL; + batadv_nc_packet_free(nc_packet); + + /* Send the coded packet and return true */ + batadv_send_skb_packet(skb_dest, neigh_node->if_incoming, first_dest); + res = true; +out: + if (router_neigh) + batadv_neigh_node_free_ref(router_neigh); + if (router_coding) + batadv_neigh_node_free_ref(router_coding); + return res; +} + +/** + * batadv_nc_skb_coding_possible - true if a decoded skb is available at dst. + * @skb: data skb to forward + * @dst: destination mac address of the other skb to code with + * @src: source mac address of skb + * + * Whenever we network code a packet we have to check whether we received it in + * a network coded form. If so, we may not be able to use it for coding because + * some neighbors may also have received (overheard) the packet in the network + * coded form without being able to decode it. It is hard to know which of the + * neighboring nodes was able to decode the packet, therefore we can only + * re-code the packet if the source of the previous encoded packet is involved. + * Since the source encoded the packet we can be certain it has all necessary + * decode information. + * + * Returns true if coding of a decoded packet is allowed. + */ +static bool batadv_nc_skb_coding_possible(struct sk_buff *skb, + uint8_t *dst, uint8_t *src) +{ + if (BATADV_SKB_CB(skb)->decoded && !batadv_compare_eth(dst, src)) + return false; + else + return true; +} + +/** + * batadv_nc_path_search - Find the coding path matching in_nc_node and + * out_nc_node to retrieve a buffered packet that can be used for coding. + * @bat_priv: the bat priv with all the soft interface information + * @in_nc_node: pointer to skb next hop's neighbor nc node + * @out_nc_node: pointer to skb source's neighbor nc node + * @skb: data skb to forward + * @eth_dst: next hop mac address of skb + * + * Returns true if coding of a decoded skb is allowed. + */ +static struct batadv_nc_packet * +batadv_nc_path_search(struct batadv_priv *bat_priv, + struct batadv_nc_node *in_nc_node, + struct batadv_nc_node *out_nc_node, + struct sk_buff *skb, + uint8_t *eth_dst) +{ + struct batadv_nc_path *nc_path, nc_path_key; + struct batadv_nc_packet *nc_packet_out = NULL; + struct batadv_nc_packet *nc_packet, *nc_packet_tmp; + struct batadv_hashtable *hash = bat_priv->nc.coding_hash; + int idx; + + if (!hash) + return NULL; + + /* Create almost path key */ + batadv_nc_hash_key_gen(&nc_path_key, in_nc_node->addr, + out_nc_node->addr); + idx = batadv_nc_hash_choose(&nc_path_key, hash->size); + + /* Check for coding opportunities in this nc_path */ + rcu_read_lock(); + hlist_for_each_entry_rcu(nc_path, &hash->table[idx], hash_entry) { + if (!batadv_compare_eth(nc_path->prev_hop, in_nc_node->addr)) + continue; + + if (!batadv_compare_eth(nc_path->next_hop, out_nc_node->addr)) + continue; + + spin_lock_bh(&nc_path->packet_list_lock); + if (list_empty(&nc_path->packet_list)) { + spin_unlock_bh(&nc_path->packet_list_lock); + continue; + } + + list_for_each_entry_safe(nc_packet, nc_packet_tmp, + &nc_path->packet_list, list) { + if (!batadv_nc_skb_coding_possible(nc_packet->skb, + eth_dst, + in_nc_node->addr)) + continue; + + /* Coding opportunity is found! */ + list_del(&nc_packet->list); + nc_packet_out = nc_packet; + break; + } + + spin_unlock_bh(&nc_path->packet_list_lock); + break; + } + rcu_read_unlock(); + + return nc_packet_out; +} + +/** + * batadv_nc_skb_src_search - Loops through the list of neighoring nodes of the + * skb's sender (may be equal to the originator). + * @bat_priv: the bat priv with all the soft interface information + * @skb: data skb to forward + * @eth_dst: next hop mac address of skb + * @eth_src: source mac address of skb + * @in_nc_node: pointer to skb next hop's neighbor nc node + * + * Returns an nc packet if a suitable coding packet was found, NULL otherwise. + */ +static struct batadv_nc_packet * +batadv_nc_skb_src_search(struct batadv_priv *bat_priv, + struct sk_buff *skb, + uint8_t *eth_dst, + uint8_t *eth_src, + struct batadv_nc_node *in_nc_node) +{ + struct batadv_orig_node *orig_node; + struct batadv_nc_node *out_nc_node; + struct batadv_nc_packet *nc_packet = NULL; + + orig_node = batadv_orig_hash_find(bat_priv, eth_src); + if (!orig_node) + return NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(out_nc_node, + &orig_node->out_coding_list, list) { + /* Check if the skb is decoded and if recoding is possible */ + if (!batadv_nc_skb_coding_possible(skb, + out_nc_node->addr, eth_src)) + continue; + + /* Search for an opportunity in this nc_path */ + nc_packet = batadv_nc_path_search(bat_priv, in_nc_node, + out_nc_node, skb, eth_dst); + if (nc_packet) + break; + } + rcu_read_unlock(); + + batadv_orig_node_free_ref(orig_node); + return nc_packet; +} + +/** + * batadv_nc_skb_store_before_coding - set the ethernet src and dst of the + * unicast skb before it is stored for use in later decoding + * @bat_priv: the bat priv with all the soft interface information + * @skb: data skb to store + * @eth_dst_new: new destination mac address of skb + */ +static void batadv_nc_skb_store_before_coding(struct batadv_priv *bat_priv, + struct sk_buff *skb, + uint8_t *eth_dst_new) +{ + struct ethhdr *ethhdr; + + /* Copy skb header to change the mac header */ + skb = pskb_copy(skb, GFP_ATOMIC); + if (!skb) + return; + + /* Set the mac header as if we actually sent the packet uncoded */ + ethhdr = (struct ethhdr *)skb_mac_header(skb); + memcpy(ethhdr->h_source, ethhdr->h_dest, ETH_ALEN); + memcpy(ethhdr->h_dest, eth_dst_new, ETH_ALEN); + + /* Set data pointer to MAC header to mimic packets from our tx path */ + skb_push(skb, ETH_HLEN); + + /* Add the packet to the decoding packet pool */ + batadv_nc_skb_store_for_decoding(bat_priv, skb); + + /* batadv_nc_skb_store_for_decoding() clones the skb, so we must free + * our ref + */ + kfree_skb(skb); +} + +/** + * batadv_nc_skb_dst_search - Loops through list of neighboring nodes to dst. + * @skb: data skb to forward + * @neigh_node: next hop to forward packet to + * @ethhdr: pointer to the ethernet header inside the skb + * + * Loops through list of neighboring nodes the next hop has a good connection to + * (receives OGMs with a sufficient quality). We need to find a neighbor of our + * next hop that potentially sent a packet which our next hop also received + * (overheard) and has stored for later decoding. + * + * Returns true if the skb was consumed (encoded packet sent) or false otherwise + */ +static bool batadv_nc_skb_dst_search(struct sk_buff *skb, + struct batadv_neigh_node *neigh_node, + struct ethhdr *ethhdr) +{ + struct net_device *netdev = neigh_node->if_incoming->soft_iface; + struct batadv_priv *bat_priv = netdev_priv(netdev); + struct batadv_orig_node *orig_node = neigh_node->orig_node; + struct batadv_nc_node *nc_node; + struct batadv_nc_packet *nc_packet = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(nc_node, &orig_node->in_coding_list, list) { + /* Search for coding opportunity with this in_nc_node */ + nc_packet = batadv_nc_skb_src_search(bat_priv, skb, + neigh_node->addr, + ethhdr->h_source, nc_node); + + /* Opportunity was found, so stop searching */ + if (nc_packet) + break; + } + rcu_read_unlock(); + + if (!nc_packet) + return false; + + /* Save packets for later decoding */ + batadv_nc_skb_store_before_coding(bat_priv, skb, + neigh_node->addr); + batadv_nc_skb_store_before_coding(bat_priv, nc_packet->skb, + nc_packet->neigh_node->addr); + + /* Code and send packets */ + if (batadv_nc_code_packets(bat_priv, skb, ethhdr, nc_packet, + neigh_node)) + return true; + + /* out of mem ? Coding failed - we have to free the buffered packet + * to avoid memleaks. The skb passed as argument will be dealt with + * by the calling function. + */ + batadv_nc_send_packet(nc_packet); + return false; +} + +/** + * batadv_nc_skb_add_to_path - buffer skb for later encoding / decoding + * @skb: skb to add to path + * @nc_path: path to add skb to + * @neigh_node: next hop to forward packet to + * @packet_id: checksum to identify packet + * + * Returns true if the packet was buffered or false in case of an error. + */ +static bool batadv_nc_skb_add_to_path(struct sk_buff *skb, + struct batadv_nc_path *nc_path, + struct batadv_neigh_node *neigh_node, + __be32 packet_id) +{ + struct batadv_nc_packet *nc_packet; + + nc_packet = kzalloc(sizeof(*nc_packet), GFP_ATOMIC); + if (!nc_packet) + return false; + + /* Initialize nc_packet */ + nc_packet->timestamp = jiffies; + nc_packet->packet_id = packet_id; + nc_packet->skb = skb; + nc_packet->neigh_node = neigh_node; + nc_packet->nc_path = nc_path; + + /* Add coding packet to list */ + spin_lock_bh(&nc_path->packet_list_lock); + list_add_tail(&nc_packet->list, &nc_path->packet_list); + spin_unlock_bh(&nc_path->packet_list_lock); + + return true; +} + +/** + * batadv_nc_skb_forward - try to code a packet or add it to the coding packet + * buffer + * @skb: data skb to forward + * @neigh_node: next hop to forward packet to + * @ethhdr: pointer to the ethernet header inside the skb + * + * Returns true if the skb was consumed (encoded packet sent) or false otherwise + */ +bool batadv_nc_skb_forward(struct sk_buff *skb, + struct batadv_neigh_node *neigh_node, + struct ethhdr *ethhdr) +{ + const struct net_device *netdev = neigh_node->if_incoming->soft_iface; + struct batadv_priv *bat_priv = netdev_priv(netdev); + struct batadv_unicast_packet *packet; + struct batadv_nc_path *nc_path; + __be32 packet_id; + u8 *payload; + + /* Check if network coding is enabled */ + if (!atomic_read(&bat_priv->network_coding)) + goto out; + + /* We only handle unicast packets */ + payload = skb_network_header(skb); + packet = (struct batadv_unicast_packet *)payload; + if (packet->header.packet_type != BATADV_UNICAST) + goto out; + + /* Try to find a coding opportunity and send the skb if one is found */ + if (batadv_nc_skb_dst_search(skb, neigh_node, ethhdr)) + return true; + + /* Find or create a nc_path for this src-dst pair */ + nc_path = batadv_nc_get_path(bat_priv, + bat_priv->nc.coding_hash, + ethhdr->h_source, + neigh_node->addr); + + if (!nc_path) + goto out; + + /* Add skb to nc_path */ + packet_id = batadv_skb_crc32(skb, payload + sizeof(*packet)); + if (!batadv_nc_skb_add_to_path(skb, nc_path, neigh_node, packet_id)) + goto free_nc_path; + + /* Packet is consumed */ + return true; + +free_nc_path: + batadv_nc_path_free_ref(nc_path); +out: + /* Packet is not consumed */ + return false; +} + +/** + * batadv_nc_skb_store_for_decoding - save a clone of the skb which can be used + * when decoding coded packets + * @bat_priv: the bat priv with all the soft interface information + * @skb: data skb to store + */ +void batadv_nc_skb_store_for_decoding(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + struct batadv_unicast_packet *packet; + struct batadv_nc_path *nc_path; + struct ethhdr *ethhdr = (struct ethhdr *)skb_mac_header(skb); + __be32 packet_id; + u8 *payload; + + /* Check if network coding is enabled */ + if (!atomic_read(&bat_priv->network_coding)) + goto out; + + /* Check for supported packet type */ + payload = skb_network_header(skb); + packet = (struct batadv_unicast_packet *)payload; + if (packet->header.packet_type != BATADV_UNICAST) + goto out; + + /* Find existing nc_path or create a new */ + nc_path = batadv_nc_get_path(bat_priv, + bat_priv->nc.decoding_hash, + ethhdr->h_source, + ethhdr->h_dest); + + if (!nc_path) + goto out; + + /* Clone skb and adjust skb->data to point at batman header */ + skb = skb_clone(skb, GFP_ATOMIC); + if (unlikely(!skb)) + goto free_nc_path; + + if (unlikely(!pskb_may_pull(skb, ETH_HLEN))) + goto free_skb; + + if (unlikely(!skb_pull_rcsum(skb, ETH_HLEN))) + goto free_skb; + + /* Add skb to nc_path */ + packet_id = batadv_skb_crc32(skb, payload + sizeof(*packet)); + if (!batadv_nc_skb_add_to_path(skb, nc_path, NULL, packet_id)) + goto free_skb; + + batadv_inc_counter(bat_priv, BATADV_CNT_NC_BUFFER); + return; + +free_skb: + kfree_skb(skb); +free_nc_path: + batadv_nc_path_free_ref(nc_path); +out: + return; +} + +/** + * batadv_nc_skb_store_sniffed_unicast - check if a received unicast packet + * should be saved in the decoding buffer and, if so, store it there + * @bat_priv: the bat priv with all the soft interface information + * @skb: unicast skb to store + */ +void batadv_nc_skb_store_sniffed_unicast(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + struct ethhdr *ethhdr = (struct ethhdr *)skb_mac_header(skb); + + if (batadv_is_my_mac(bat_priv, ethhdr->h_dest)) + return; + + /* Set data pointer to MAC header to mimic packets from our tx path */ + skb_push(skb, ETH_HLEN); + + batadv_nc_skb_store_for_decoding(bat_priv, skb); +} + +/** + * batadv_nc_skb_decode_packet - decode given skb using the decode data stored + * in nc_packet + * @bat_priv: the bat priv with all the soft interface information + * @skb: unicast skb to decode + * @nc_packet: decode data needed to decode the skb + * + * Returns pointer to decoded unicast packet if the packet was decoded or NULL + * in case of an error. + */ +static struct batadv_unicast_packet * +batadv_nc_skb_decode_packet(struct batadv_priv *bat_priv, struct sk_buff *skb, + struct batadv_nc_packet *nc_packet) +{ + const int h_size = sizeof(struct batadv_unicast_packet); + const int h_diff = sizeof(struct batadv_coded_packet) - h_size; + struct batadv_unicast_packet *unicast_packet; + struct batadv_coded_packet coded_packet_tmp; + struct ethhdr *ethhdr, ethhdr_tmp; + uint8_t *orig_dest, ttl, ttvn; + unsigned int coding_len; + int err; + + /* Save headers temporarily */ + memcpy(&coded_packet_tmp, skb->data, sizeof(coded_packet_tmp)); + memcpy(ðhdr_tmp, skb_mac_header(skb), sizeof(ethhdr_tmp)); + + if (skb_cow(skb, 0) < 0) + return NULL; + + if (unlikely(!skb_pull_rcsum(skb, h_diff))) + return NULL; + + /* Data points to batman header, so set mac header 14 bytes before + * and network to data + */ + skb_set_mac_header(skb, -ETH_HLEN); + skb_reset_network_header(skb); + + /* Reconstruct original mac header */ + ethhdr = (struct ethhdr *)skb_mac_header(skb); + memcpy(ethhdr, ðhdr_tmp, sizeof(*ethhdr)); + + /* Select the correct unicast header information based on the location + * of our mac address in the coded_packet header + */ + if (batadv_is_my_mac(bat_priv, coded_packet_tmp.second_dest)) { + /* If we are the second destination the packet was overheard, + * so the Ethernet address must be copied to h_dest and + * pkt_type changed from PACKET_OTHERHOST to PACKET_HOST + */ + memcpy(ethhdr->h_dest, coded_packet_tmp.second_dest, ETH_ALEN); + skb->pkt_type = PACKET_HOST; + + orig_dest = coded_packet_tmp.second_orig_dest; + ttl = coded_packet_tmp.second_ttl; + ttvn = coded_packet_tmp.second_ttvn; + } else { + orig_dest = coded_packet_tmp.first_orig_dest; + ttl = coded_packet_tmp.header.ttl; + ttvn = coded_packet_tmp.first_ttvn; + } + + coding_len = ntohs(coded_packet_tmp.coded_len); + + if (coding_len > skb->len) + return NULL; + + /* Here the magic is reversed: + * extract the missing packet from the received coded packet + */ + batadv_nc_memxor(skb->data + h_size, + nc_packet->skb->data + h_size, + coding_len); + + /* Resize decoded skb if decoded with larger packet */ + if (nc_packet->skb->len > coding_len + h_size) { + err = pskb_trim_rcsum(skb, coding_len + h_size); + if (err) + return NULL; + } + + /* Create decoded unicast packet */ + unicast_packet = (struct batadv_unicast_packet *)skb->data; + unicast_packet->header.packet_type = BATADV_UNICAST; + unicast_packet->header.version = BATADV_COMPAT_VERSION; + unicast_packet->header.ttl = ttl; + memcpy(unicast_packet->dest, orig_dest, ETH_ALEN); + unicast_packet->ttvn = ttvn; + + batadv_nc_packet_free(nc_packet); + return unicast_packet; +} + +/** + * batadv_nc_find_decoding_packet - search through buffered decoding data to + * find the data needed to decode the coded packet + * @bat_priv: the bat priv with all the soft interface information + * @ethhdr: pointer to the ethernet header inside the coded packet + * @coded: coded packet we try to find decode data for + * + * Returns pointer to nc packet if the needed data was found or NULL otherwise. + */ +static struct batadv_nc_packet * +batadv_nc_find_decoding_packet(struct batadv_priv *bat_priv, + struct ethhdr *ethhdr, + struct batadv_coded_packet *coded) +{ + struct batadv_hashtable *hash = bat_priv->nc.decoding_hash; + struct batadv_nc_packet *tmp_nc_packet, *nc_packet = NULL; + struct batadv_nc_path *nc_path, nc_path_key; + uint8_t *dest, *source; + __be32 packet_id; + int index; + + if (!hash) + return NULL; + + /* Select the correct packet id based on the location of our mac-addr */ + dest = ethhdr->h_source; + if (!batadv_is_my_mac(bat_priv, coded->second_dest)) { + source = coded->second_source; + packet_id = coded->second_crc; + } else { + source = coded->first_source; + packet_id = coded->first_crc; + } + + batadv_nc_hash_key_gen(&nc_path_key, source, dest); + index = batadv_nc_hash_choose(&nc_path_key, hash->size); + + /* Search for matching coding path */ + rcu_read_lock(); + hlist_for_each_entry_rcu(nc_path, &hash->table[index], hash_entry) { + /* Find matching nc_packet */ + spin_lock_bh(&nc_path->packet_list_lock); + list_for_each_entry(tmp_nc_packet, + &nc_path->packet_list, list) { + if (packet_id == tmp_nc_packet->packet_id) { + list_del(&tmp_nc_packet->list); + + nc_packet = tmp_nc_packet; + break; + } + } + spin_unlock_bh(&nc_path->packet_list_lock); + + if (nc_packet) + break; + } + rcu_read_unlock(); + + if (!nc_packet) + batadv_dbg(BATADV_DBG_NC, bat_priv, + "No decoding packet found for %u\n", packet_id); + + return nc_packet; +} + +/** + * batadv_nc_recv_coded_packet - try to decode coded packet and enqueue the + * resulting unicast packet + * @skb: incoming coded packet + * @recv_if: pointer to interface this packet was received on + */ +static int batadv_nc_recv_coded_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_unicast_packet *unicast_packet; + struct batadv_coded_packet *coded_packet; + struct batadv_nc_packet *nc_packet; + struct ethhdr *ethhdr; + int hdr_size = sizeof(*coded_packet); + + /* Check if network coding is enabled */ + if (!atomic_read(&bat_priv->network_coding)) + return NET_RX_DROP; + + /* Make sure we can access (and remove) header */ + if (unlikely(!pskb_may_pull(skb, hdr_size))) + return NET_RX_DROP; + + coded_packet = (struct batadv_coded_packet *)skb->data; + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + /* Verify frame is destined for us */ + if (!batadv_is_my_mac(bat_priv, ethhdr->h_dest) && + !batadv_is_my_mac(bat_priv, coded_packet->second_dest)) + return NET_RX_DROP; + + /* Update stat counter */ + if (batadv_is_my_mac(bat_priv, coded_packet->second_dest)) + batadv_inc_counter(bat_priv, BATADV_CNT_NC_SNIFFED); + + nc_packet = batadv_nc_find_decoding_packet(bat_priv, ethhdr, + coded_packet); + if (!nc_packet) { + batadv_inc_counter(bat_priv, BATADV_CNT_NC_DECODE_FAILED); + return NET_RX_DROP; + } + + /* Make skb's linear, because decoding accesses the entire buffer */ + if (skb_linearize(skb) < 0) + goto free_nc_packet; + + if (skb_linearize(nc_packet->skb) < 0) + goto free_nc_packet; + + /* Decode the packet */ + unicast_packet = batadv_nc_skb_decode_packet(bat_priv, skb, nc_packet); + if (!unicast_packet) { + batadv_inc_counter(bat_priv, BATADV_CNT_NC_DECODE_FAILED); + goto free_nc_packet; + } + + /* Mark packet as decoded to do correct recoding when forwarding */ + BATADV_SKB_CB(skb)->decoded = true; + batadv_inc_counter(bat_priv, BATADV_CNT_NC_DECODE); + batadv_add_counter(bat_priv, BATADV_CNT_NC_DECODE_BYTES, + skb->len + ETH_HLEN); + return batadv_recv_unicast_packet(skb, recv_if); + +free_nc_packet: + batadv_nc_packet_free(nc_packet); + return NET_RX_DROP; +} + +/** + * batadv_nc_mesh_free - clean up network coding memory + * @bat_priv: the bat priv with all the soft interface information + */ +void batadv_nc_mesh_free(struct batadv_priv *bat_priv) +{ + cancel_delayed_work_sync(&bat_priv->nc.work); + + batadv_nc_purge_paths(bat_priv, bat_priv->nc.coding_hash, NULL); + batadv_hash_destroy(bat_priv->nc.coding_hash); + batadv_nc_purge_paths(bat_priv, bat_priv->nc.decoding_hash, NULL); + batadv_hash_destroy(bat_priv->nc.decoding_hash); +} + +/** + * batadv_nc_nodes_seq_print_text - print the nc node information + * @seq: seq file to print on + * @offset: not used + */ +int batadv_nc_nodes_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->orig_hash; + struct batadv_hard_iface *primary_if; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + struct batadv_nc_node *nc_node; + int i; + + primary_if = batadv_seq_print_text_primary_if_get(seq); + if (!primary_if) + goto out; + + /* Traverse list of originators */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + /* For each orig_node in this bin */ + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + seq_printf(seq, "Node: %pM\n", orig_node->orig); + + seq_puts(seq, " Ingoing: "); + /* For each in_nc_node to this orig_node */ + list_for_each_entry_rcu(nc_node, + &orig_node->in_coding_list, + list) + seq_printf(seq, "%pM ", + nc_node->addr); + seq_puts(seq, "\n"); + + seq_puts(seq, " Outgoing: "); + /* For out_nc_node to this orig_node */ + list_for_each_entry_rcu(nc_node, + &orig_node->out_coding_list, + list) + seq_printf(seq, "%pM ", + nc_node->addr); + seq_puts(seq, "\n\n"); + } + rcu_read_unlock(); + } + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return 0; +} + +/** + * batadv_nc_init_debugfs - create nc folder and related files in debugfs + * @bat_priv: the bat priv with all the soft interface information + */ +int batadv_nc_init_debugfs(struct batadv_priv *bat_priv) +{ + struct dentry *nc_dir, *file; + + nc_dir = debugfs_create_dir("nc", bat_priv->debug_dir); + if (!nc_dir) + goto out; + + file = debugfs_create_u8("min_tq", S_IRUGO | S_IWUSR, nc_dir, + &bat_priv->nc.min_tq); + if (!file) + goto out; + + file = debugfs_create_u32("max_fwd_delay", S_IRUGO | S_IWUSR, nc_dir, + &bat_priv->nc.max_fwd_delay); + if (!file) + goto out; + + file = debugfs_create_u32("max_buffer_time", S_IRUGO | S_IWUSR, nc_dir, + &bat_priv->nc.max_buffer_time); + if (!file) + goto out; + + return 0; + +out: + return -ENOMEM; +} diff --git a/net/batman-adv/network-coding.h b/net/batman-adv/network-coding.h new file mode 100644 index 00000000..bd4295fb --- /dev/null +++ b/net/batman-adv/network-coding.h @@ -0,0 +1,129 @@ +/* Copyright (C) 2012-2013 B.A.T.M.A.N. contributors: + * + * Martin Hundebøll, Jeppe Ledet-Pedersen + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_NETWORK_CODING_H_ +#define _NET_BATMAN_ADV_NETWORK_CODING_H_ + +#ifdef CONFIG_BATMAN_ADV_NC + +int batadv_nc_init(void); +int batadv_nc_mesh_init(struct batadv_priv *bat_priv); +void batadv_nc_mesh_free(struct batadv_priv *bat_priv); +void batadv_nc_update_nc_node(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + struct batadv_ogm_packet *ogm_packet, + int is_single_hop_neigh); +void batadv_nc_purge_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + bool (*to_purge)(struct batadv_priv *, + struct batadv_nc_node *)); +void batadv_nc_init_bat_priv(struct batadv_priv *bat_priv); +void batadv_nc_init_orig(struct batadv_orig_node *orig_node); +bool batadv_nc_skb_forward(struct sk_buff *skb, + struct batadv_neigh_node *neigh_node, + struct ethhdr *ethhdr); +void batadv_nc_skb_store_for_decoding(struct batadv_priv *bat_priv, + struct sk_buff *skb); +void batadv_nc_skb_store_sniffed_unicast(struct batadv_priv *bat_priv, + struct sk_buff *skb); +int batadv_nc_nodes_seq_print_text(struct seq_file *seq, void *offset); +int batadv_nc_init_debugfs(struct batadv_priv *bat_priv); + +#else /* ifdef CONFIG_BATMAN_ADV_NC */ + +static inline int batadv_nc_init(void) +{ + return 0; +} + +static inline int batadv_nc_mesh_init(struct batadv_priv *bat_priv) +{ + return 0; +} + +static inline void batadv_nc_mesh_free(struct batadv_priv *bat_priv) +{ + return; +} + +static inline void +batadv_nc_update_nc_node(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + struct batadv_ogm_packet *ogm_packet, + int is_single_hop_neigh) +{ + return; +} + +static inline void +batadv_nc_purge_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + bool (*to_purge)(struct batadv_priv *, + struct batadv_nc_node *)) +{ + return; +} + +static inline void batadv_nc_init_bat_priv(struct batadv_priv *bat_priv) +{ + return; +} + +static inline void batadv_nc_init_orig(struct batadv_orig_node *orig_node) +{ + return; +} + +static inline bool batadv_nc_skb_forward(struct sk_buff *skb, + struct batadv_neigh_node *neigh_node, + struct ethhdr *ethhdr) +{ + return false; +} + +static inline void +batadv_nc_skb_store_for_decoding(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + return; +} + +static inline void +batadv_nc_skb_store_sniffed_unicast(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + return; +} + +static inline int batadv_nc_nodes_seq_print_text(struct seq_file *seq, + void *offset) +{ + return 0; +} + +static inline int batadv_nc_init_debugfs(struct batadv_priv *bat_priv) +{ + return 0; +} + +#endif /* ifdef CONFIG_BATMAN_ADV_NC */ + +#endif /* _NET_BATMAN_ADV_NETWORK_CODING_H_ */ diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c new file mode 100644 index 00000000..fad1a209 --- /dev/null +++ b/net/batman-adv/originator.c @@ -0,0 +1,666 @@ +/* Copyright (C) 2009-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 "distributed-arp-table.h" +#include "originator.h" +#include "hash.h" +#include "translation-table.h" +#include "routing.h" +#include "gateway_client.h" +#include "hard-interface.h" +#include "unicast.h" +#include "soft-interface.h" +#include "bridge_loop_avoidance.h" +#include "network-coding.h" + +/* hash class keys */ +static struct lock_class_key batadv_orig_hash_lock_class_key; + +static void batadv_purge_orig(struct work_struct *work); + +/* returns 1 if they are the same originator */ +static int batadv_compare_orig(const struct hlist_node *node, const void *data2) +{ + const void *data1 = container_of(node, struct batadv_orig_node, + hash_entry); + + return (memcmp(data1, data2, ETH_ALEN) == 0 ? 1 : 0); +} + +int batadv_originator_init(struct batadv_priv *bat_priv) +{ + if (bat_priv->orig_hash) + return 0; + + bat_priv->orig_hash = batadv_hash_new(1024); + + if (!bat_priv->orig_hash) + goto err; + + batadv_hash_set_lock_class(bat_priv->orig_hash, + &batadv_orig_hash_lock_class_key); + + INIT_DELAYED_WORK(&bat_priv->orig_work, batadv_purge_orig); + queue_delayed_work(batadv_event_workqueue, + &bat_priv->orig_work, + msecs_to_jiffies(BATADV_ORIG_WORK_PERIOD)); + + return 0; + +err: + return -ENOMEM; +} + +void batadv_neigh_node_free_ref(struct batadv_neigh_node *neigh_node) +{ + if (atomic_dec_and_test(&neigh_node->refcount)) + kfree_rcu(neigh_node, rcu); +} + +/* increases the refcounter of a found router */ +struct batadv_neigh_node * +batadv_orig_node_get_router(struct batadv_orig_node *orig_node) +{ + struct batadv_neigh_node *router; + + rcu_read_lock(); + router = rcu_dereference(orig_node->router); + + if (router && !atomic_inc_not_zero(&router->refcount)) + router = NULL; + + rcu_read_unlock(); + return router; +} + +struct batadv_neigh_node * +batadv_neigh_node_new(struct batadv_hard_iface *hard_iface, + const uint8_t *neigh_addr, uint32_t seqno) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + struct batadv_neigh_node *neigh_node; + + neigh_node = kzalloc(sizeof(*neigh_node), GFP_ATOMIC); + if (!neigh_node) + goto out; + + INIT_HLIST_NODE(&neigh_node->list); + + memcpy(neigh_node->addr, neigh_addr, ETH_ALEN); + spin_lock_init(&neigh_node->lq_update_lock); + + /* extra reference for return */ + atomic_set(&neigh_node->refcount, 2); + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Creating new neighbor %pM, initial seqno %d\n", + neigh_addr, seqno); + +out: + return neigh_node; +} + +static void batadv_orig_node_free_rcu(struct rcu_head *rcu) +{ + struct hlist_node *node_tmp; + struct batadv_neigh_node *neigh_node, *tmp_neigh_node; + struct batadv_orig_node *orig_node; + + orig_node = container_of(rcu, struct batadv_orig_node, rcu); + + spin_lock_bh(&orig_node->neigh_list_lock); + + /* for all bonding members ... */ + list_for_each_entry_safe(neigh_node, tmp_neigh_node, + &orig_node->bond_list, bonding_list) { + list_del_rcu(&neigh_node->bonding_list); + batadv_neigh_node_free_ref(neigh_node); + } + + /* for all neighbors towards this originator ... */ + hlist_for_each_entry_safe(neigh_node, node_tmp, + &orig_node->neigh_list, list) { + hlist_del_rcu(&neigh_node->list); + batadv_neigh_node_free_ref(neigh_node); + } + + spin_unlock_bh(&orig_node->neigh_list_lock); + + /* Free nc_nodes */ + batadv_nc_purge_orig(orig_node->bat_priv, orig_node, NULL); + + batadv_frag_list_free(&orig_node->frag_list); + batadv_tt_global_del_orig(orig_node->bat_priv, orig_node, + "originator timed out"); + + kfree(orig_node->tt_buff); + kfree(orig_node->bcast_own); + kfree(orig_node->bcast_own_sum); + kfree(orig_node); +} + +/** + * batadv_orig_node_free_ref - decrement the orig node refcounter and possibly + * schedule an rcu callback for freeing it + * @orig_node: the orig node to free + */ +void batadv_orig_node_free_ref(struct batadv_orig_node *orig_node) +{ + if (atomic_dec_and_test(&orig_node->refcount)) + call_rcu(&orig_node->rcu, batadv_orig_node_free_rcu); +} + +/** + * batadv_orig_node_free_ref_now - decrement the orig node refcounter and + * possibly free it (without rcu callback) + * @orig_node: the orig node to free + */ +void batadv_orig_node_free_ref_now(struct batadv_orig_node *orig_node) +{ + if (atomic_dec_and_test(&orig_node->refcount)) + batadv_orig_node_free_rcu(&orig_node->rcu); +} + +void batadv_originator_free(struct batadv_priv *bat_priv) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_node *node_tmp; + struct hlist_head *head; + spinlock_t *list_lock; /* spinlock to protect write access */ + struct batadv_orig_node *orig_node; + uint32_t i; + + if (!hash) + return; + + cancel_delayed_work_sync(&bat_priv->orig_work); + + bat_priv->orig_hash = NULL; + + 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(orig_node, node_tmp, + head, hash_entry) { + hlist_del_rcu(&orig_node->hash_entry); + batadv_orig_node_free_ref(orig_node); + } + spin_unlock_bh(list_lock); + } + + batadv_hash_destroy(hash); +} + +/* this function finds or creates an originator entry for the given + * address if it does not exits + */ +struct batadv_orig_node *batadv_get_orig_node(struct batadv_priv *bat_priv, + const uint8_t *addr) +{ + struct batadv_orig_node *orig_node; + int size; + int hash_added; + unsigned long reset_time; + + orig_node = batadv_orig_hash_find(bat_priv, addr); + if (orig_node) + return orig_node; + + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Creating new originator: %pM\n", addr); + + orig_node = kzalloc(sizeof(*orig_node), GFP_ATOMIC); + if (!orig_node) + return NULL; + + INIT_HLIST_HEAD(&orig_node->neigh_list); + INIT_LIST_HEAD(&orig_node->bond_list); + spin_lock_init(&orig_node->ogm_cnt_lock); + spin_lock_init(&orig_node->bcast_seqno_lock); + spin_lock_init(&orig_node->neigh_list_lock); + spin_lock_init(&orig_node->tt_buff_lock); + + batadv_nc_init_orig(orig_node); + + /* extra reference for return */ + atomic_set(&orig_node->refcount, 2); + + orig_node->tt_initialised = false; + orig_node->bat_priv = bat_priv; + memcpy(orig_node->orig, addr, ETH_ALEN); + batadv_dat_init_orig_node_addr(orig_node); + orig_node->router = NULL; + orig_node->tt_crc = 0; + atomic_set(&orig_node->last_ttvn, 0); + orig_node->tt_buff = NULL; + orig_node->tt_buff_len = 0; + atomic_set(&orig_node->tt_size, 0); + reset_time = jiffies - 1 - msecs_to_jiffies(BATADV_RESET_PROTECTION_MS); + orig_node->bcast_seqno_reset = reset_time; + orig_node->batman_seqno_reset = reset_time; + + atomic_set(&orig_node->bond_candidates, 0); + + size = bat_priv->num_ifaces * sizeof(unsigned long) * BATADV_NUM_WORDS; + + orig_node->bcast_own = kzalloc(size, GFP_ATOMIC); + if (!orig_node->bcast_own) + goto free_orig_node; + + size = bat_priv->num_ifaces * sizeof(uint8_t); + orig_node->bcast_own_sum = kzalloc(size, GFP_ATOMIC); + + INIT_LIST_HEAD(&orig_node->frag_list); + orig_node->last_frag_packet = 0; + + if (!orig_node->bcast_own_sum) + goto free_bcast_own; + + hash_added = batadv_hash_add(bat_priv->orig_hash, batadv_compare_orig, + batadv_choose_orig, orig_node, + &orig_node->hash_entry); + if (hash_added != 0) + goto free_bcast_own_sum; + + return orig_node; +free_bcast_own_sum: + kfree(orig_node->bcast_own_sum); +free_bcast_own: + kfree(orig_node->bcast_own); +free_orig_node: + kfree(orig_node); + return NULL; +} + +static bool +batadv_purge_orig_neighbors(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_neigh_node **best_neigh_node) +{ + struct hlist_node *node_tmp; + struct batadv_neigh_node *neigh_node; + bool neigh_purged = false; + unsigned long last_seen; + struct batadv_hard_iface *if_incoming; + + *best_neigh_node = NULL; + + spin_lock_bh(&orig_node->neigh_list_lock); + + /* for all neighbors towards this originator ... */ + hlist_for_each_entry_safe(neigh_node, node_tmp, + &orig_node->neigh_list, list) { + last_seen = neigh_node->last_seen; + if_incoming = neigh_node->if_incoming; + + if ((batadv_has_timed_out(last_seen, BATADV_PURGE_TIMEOUT)) || + (if_incoming->if_status == BATADV_IF_INACTIVE) || + (if_incoming->if_status == BATADV_IF_NOT_IN_USE) || + (if_incoming->if_status == BATADV_IF_TO_BE_REMOVED)) { + if ((if_incoming->if_status == BATADV_IF_INACTIVE) || + (if_incoming->if_status == BATADV_IF_NOT_IN_USE) || + (if_incoming->if_status == BATADV_IF_TO_BE_REMOVED)) + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "neighbor purge: originator %pM, neighbor: %pM, iface: %s\n", + orig_node->orig, neigh_node->addr, + if_incoming->net_dev->name); + else + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "neighbor timeout: originator %pM, neighbor: %pM, last_seen: %u\n", + orig_node->orig, neigh_node->addr, + jiffies_to_msecs(last_seen)); + + neigh_purged = true; + + hlist_del_rcu(&neigh_node->list); + batadv_bonding_candidate_del(orig_node, neigh_node); + batadv_neigh_node_free_ref(neigh_node); + } else { + if ((!*best_neigh_node) || + (neigh_node->tq_avg > (*best_neigh_node)->tq_avg)) + *best_neigh_node = neigh_node; + } + } + + spin_unlock_bh(&orig_node->neigh_list_lock); + return neigh_purged; +} + +static bool batadv_purge_orig_node(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node) +{ + struct batadv_neigh_node *best_neigh_node; + + if (batadv_has_timed_out(orig_node->last_seen, + 2 * BATADV_PURGE_TIMEOUT)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Originator timeout: originator %pM, last_seen %u\n", + orig_node->orig, + jiffies_to_msecs(orig_node->last_seen)); + return true; + } else { + if (batadv_purge_orig_neighbors(bat_priv, orig_node, + &best_neigh_node)) + batadv_update_route(bat_priv, orig_node, + best_neigh_node); + } + + return false; +} + +static void _batadv_purge_orig(struct batadv_priv *bat_priv) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_node *node_tmp; + struct hlist_head *head; + spinlock_t *list_lock; /* spinlock to protect write access */ + struct batadv_orig_node *orig_node; + uint32_t i; + + if (!hash) + return; + + /* for all origins... */ + 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(orig_node, node_tmp, + head, hash_entry) { + if (batadv_purge_orig_node(bat_priv, orig_node)) { + if (orig_node->gw_flags) + batadv_gw_node_delete(bat_priv, + orig_node); + hlist_del_rcu(&orig_node->hash_entry); + batadv_orig_node_free_ref(orig_node); + continue; + } + + if (batadv_has_timed_out(orig_node->last_frag_packet, + BATADV_FRAG_TIMEOUT)) + batadv_frag_list_free(&orig_node->frag_list); + } + spin_unlock_bh(list_lock); + } + + batadv_gw_node_purge(bat_priv); + batadv_gw_election(bat_priv); +} + +static void batadv_purge_orig(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct batadv_priv *bat_priv; + + delayed_work = container_of(work, struct delayed_work, work); + bat_priv = container_of(delayed_work, struct batadv_priv, orig_work); + _batadv_purge_orig(bat_priv); + queue_delayed_work(batadv_event_workqueue, + &bat_priv->orig_work, + msecs_to_jiffies(BATADV_ORIG_WORK_PERIOD)); +} + +void batadv_purge_orig_ref(struct batadv_priv *bat_priv) +{ + _batadv_purge_orig(bat_priv); +} + +int batadv_orig_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->orig_hash; + struct hlist_head *head; + struct batadv_hard_iface *primary_if; + struct batadv_orig_node *orig_node; + struct batadv_neigh_node *neigh_node, *neigh_node_tmp; + int batman_count = 0; + int last_seen_secs; + int last_seen_msecs; + unsigned long last_seen_jiffies; + uint32_t i; + + primary_if = batadv_seq_print_text_primary_if_get(seq); + if (!primary_if) + goto out; + + seq_printf(seq, "[B.A.T.M.A.N. adv %s, MainIF/MAC: %s/%pM (%s)]\n", + BATADV_SOURCE_VERSION, primary_if->net_dev->name, + primary_if->net_dev->dev_addr, net_dev->name); + seq_printf(seq, " %-15s %s (%s/%i) %17s [%10s]: %20s ...\n", + "Originator", "last-seen", "#", BATADV_TQ_MAX_VALUE, + "Nexthop", "outgoingIF", "Potential nexthops"); + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + neigh_node = batadv_orig_node_get_router(orig_node); + if (!neigh_node) + continue; + + if (neigh_node->tq_avg == 0) + goto next; + + last_seen_jiffies = jiffies - orig_node->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; + + seq_printf(seq, "%pM %4i.%03is (%3i) %pM [%10s]:", + orig_node->orig, last_seen_secs, + last_seen_msecs, neigh_node->tq_avg, + neigh_node->addr, + neigh_node->if_incoming->net_dev->name); + + hlist_for_each_entry_rcu(neigh_node_tmp, + &orig_node->neigh_list, list) { + seq_printf(seq, " %pM (%3i)", + neigh_node_tmp->addr, + neigh_node_tmp->tq_avg); + } + + seq_puts(seq, "\n"); + batman_count++; + +next: + batadv_neigh_node_free_ref(neigh_node); + } + rcu_read_unlock(); + } + + if (batman_count == 0) + seq_puts(seq, "No batman nodes in range ...\n"); + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return 0; +} + +static int batadv_orig_node_add_if(struct batadv_orig_node *orig_node, + int max_if_num) +{ + void *data_ptr; + size_t data_size, old_size; + + data_size = max_if_num * sizeof(unsigned long) * BATADV_NUM_WORDS; + old_size = (max_if_num - 1) * sizeof(unsigned long) * BATADV_NUM_WORDS; + data_ptr = kmalloc(data_size, GFP_ATOMIC); + if (!data_ptr) + return -ENOMEM; + + memcpy(data_ptr, orig_node->bcast_own, old_size); + kfree(orig_node->bcast_own); + orig_node->bcast_own = data_ptr; + + data_ptr = kmalloc(max_if_num * sizeof(uint8_t), GFP_ATOMIC); + if (!data_ptr) + return -ENOMEM; + + memcpy(data_ptr, orig_node->bcast_own_sum, + (max_if_num - 1) * sizeof(uint8_t)); + kfree(orig_node->bcast_own_sum); + orig_node->bcast_own_sum = data_ptr; + + return 0; +} + +int batadv_orig_hash_add_if(struct batadv_hard_iface *hard_iface, + int max_if_num) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + uint32_t i; + int ret; + + /* resize all orig nodes because orig_node->bcast_own(_sum) depend on + * if_num + */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + spin_lock_bh(&orig_node->ogm_cnt_lock); + ret = batadv_orig_node_add_if(orig_node, max_if_num); + spin_unlock_bh(&orig_node->ogm_cnt_lock); + + if (ret == -ENOMEM) + goto err; + } + rcu_read_unlock(); + } + + return 0; + +err: + rcu_read_unlock(); + return -ENOMEM; +} + +static int batadv_orig_node_del_if(struct batadv_orig_node *orig_node, + int max_if_num, int del_if_num) +{ + void *data_ptr = NULL; + int chunk_size; + + /* last interface was removed */ + if (max_if_num == 0) + goto free_bcast_own; + + chunk_size = sizeof(unsigned long) * BATADV_NUM_WORDS; + data_ptr = kmalloc(max_if_num * chunk_size, GFP_ATOMIC); + if (!data_ptr) + return -ENOMEM; + + /* copy first part */ + memcpy(data_ptr, orig_node->bcast_own, del_if_num * chunk_size); + + /* copy second part */ + memcpy((char *)data_ptr + del_if_num * chunk_size, + orig_node->bcast_own + ((del_if_num + 1) * chunk_size), + (max_if_num - del_if_num) * chunk_size); + +free_bcast_own: + kfree(orig_node->bcast_own); + orig_node->bcast_own = data_ptr; + + if (max_if_num == 0) + goto free_own_sum; + + data_ptr = kmalloc(max_if_num * sizeof(uint8_t), GFP_ATOMIC); + if (!data_ptr) + return -ENOMEM; + + memcpy(data_ptr, orig_node->bcast_own_sum, + del_if_num * sizeof(uint8_t)); + + memcpy((char *)data_ptr + del_if_num * sizeof(uint8_t), + orig_node->bcast_own_sum + ((del_if_num + 1) * sizeof(uint8_t)), + (max_if_num - del_if_num) * sizeof(uint8_t)); + +free_own_sum: + kfree(orig_node->bcast_own_sum); + orig_node->bcast_own_sum = data_ptr; + + return 0; +} + +int batadv_orig_hash_del_if(struct batadv_hard_iface *hard_iface, + int max_if_num) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_hard_iface *hard_iface_tmp; + struct batadv_orig_node *orig_node; + uint32_t i; + int ret; + + /* resize all orig nodes because orig_node->bcast_own(_sum) depend on + * if_num + */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + spin_lock_bh(&orig_node->ogm_cnt_lock); + ret = batadv_orig_node_del_if(orig_node, max_if_num, + hard_iface->if_num); + spin_unlock_bh(&orig_node->ogm_cnt_lock); + + if (ret == -ENOMEM) + goto err; + } + rcu_read_unlock(); + } + + /* renumber remaining batman interfaces _inside_ of orig_hash_lock */ + rcu_read_lock(); + list_for_each_entry_rcu(hard_iface_tmp, &batadv_hardif_list, list) { + if (hard_iface_tmp->if_status == BATADV_IF_NOT_IN_USE) + continue; + + if (hard_iface == hard_iface_tmp) + continue; + + if (hard_iface->soft_iface != hard_iface_tmp->soft_iface) + continue; + + if (hard_iface_tmp->if_num > hard_iface->if_num) + hard_iface_tmp->if_num--; + } + rcu_read_unlock(); + + hard_iface->if_num = -1; + return 0; + +err: + rcu_read_unlock(); + return -ENOMEM; +} diff --git a/net/batman-adv/originator.h b/net/batman-adv/originator.h new file mode 100644 index 00000000..734e5a3d --- /dev/null +++ b/net/batman-adv/originator.h @@ -0,0 +1,97 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_ORIGINATOR_H_ +#define _NET_BATMAN_ADV_ORIGINATOR_H_ + +#include "hash.h" + +int batadv_originator_init(struct batadv_priv *bat_priv); +void batadv_originator_free(struct batadv_priv *bat_priv); +void batadv_purge_orig_ref(struct batadv_priv *bat_priv); +void batadv_orig_node_free_ref(struct batadv_orig_node *orig_node); +void batadv_orig_node_free_ref_now(struct batadv_orig_node *orig_node); +struct batadv_orig_node *batadv_get_orig_node(struct batadv_priv *bat_priv, + const uint8_t *addr); +struct batadv_neigh_node * +batadv_neigh_node_new(struct batadv_hard_iface *hard_iface, + const uint8_t *neigh_addr, uint32_t seqno); +void batadv_neigh_node_free_ref(struct batadv_neigh_node *neigh_node); +struct batadv_neigh_node * +batadv_orig_node_get_router(struct batadv_orig_node *orig_node); +int batadv_orig_seq_print_text(struct seq_file *seq, void *offset); +int batadv_orig_hash_add_if(struct batadv_hard_iface *hard_iface, + int max_if_num); +int batadv_orig_hash_del_if(struct batadv_hard_iface *hard_iface, + int max_if_num); + + +/* hashfunction to choose an entry in a hash table of given size + * hash algorithm from http://en.wikipedia.org/wiki/Hash_table + */ +static inline uint32_t batadv_choose_orig(const void *data, uint32_t size) +{ + const unsigned char *key = data; + uint32_t hash = 0; + size_t i; + + for (i = 0; i < 6; i++) { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash % size; +} + +static inline struct batadv_orig_node * +batadv_orig_hash_find(struct batadv_priv *bat_priv, const void *data) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_orig_node *orig_node, *orig_node_tmp = NULL; + int index; + + if (!hash) + return NULL; + + index = batadv_choose_orig(data, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + if (!batadv_compare_eth(orig_node, data)) + continue; + + if (!atomic_inc_not_zero(&orig_node->refcount)) + continue; + + orig_node_tmp = orig_node; + break; + } + rcu_read_unlock(); + + return orig_node_tmp; +} + +#endif /* _NET_BATMAN_ADV_ORIGINATOR_H_ */ diff --git a/net/batman-adv/packet.h b/net/batman-adv/packet.h new file mode 100644 index 00000000..a51ccfc3 --- /dev/null +++ b/net/batman-adv/packet.h @@ -0,0 +1,314 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_PACKET_H_ +#define _NET_BATMAN_ADV_PACKET_H_ + +enum batadv_packettype { + BATADV_IV_OGM = 0x01, + BATADV_ICMP = 0x02, + BATADV_UNICAST = 0x03, + BATADV_BCAST = 0x04, + BATADV_VIS = 0x05, + BATADV_UNICAST_FRAG = 0x06, + BATADV_TT_QUERY = 0x07, + BATADV_ROAM_ADV = 0x08, + BATADV_UNICAST_4ADDR = 0x09, + BATADV_CODED = 0x0a, +}; + +/** + * enum batadv_subtype - packet subtype for unicast4addr + * @BATADV_P_DATA: user payload + * @BATADV_P_DAT_DHT_GET: DHT request message + * @BATADV_P_DAT_DHT_PUT: DHT store message + * @BATADV_P_DAT_CACHE_REPLY: ARP reply generated by DAT + */ +enum batadv_subtype { + BATADV_P_DATA = 0x01, + BATADV_P_DAT_DHT_GET = 0x02, + BATADV_P_DAT_DHT_PUT = 0x03, + BATADV_P_DAT_CACHE_REPLY = 0x04, +}; + +/* this file is included by batctl which needs these defines */ +#define BATADV_COMPAT_VERSION 14 + +enum batadv_iv_flags { + BATADV_NOT_BEST_NEXT_HOP = BIT(3), + BATADV_PRIMARIES_FIRST_HOP = BIT(4), + BATADV_VIS_SERVER = BIT(5), + BATADV_DIRECTLINK = BIT(6), +}; + +/* ICMP message types */ +enum batadv_icmp_packettype { + BATADV_ECHO_REPLY = 0, + BATADV_DESTINATION_UNREACHABLE = 3, + BATADV_ECHO_REQUEST = 8, + BATADV_TTL_EXCEEDED = 11, + BATADV_PARAMETER_PROBLEM = 12, +}; + +/* vis defines */ +enum batadv_vis_packettype { + BATADV_VIS_TYPE_SERVER_SYNC = 0, + BATADV_VIS_TYPE_CLIENT_UPDATE = 1, +}; + +/* fragmentation defines */ +enum batadv_unicast_frag_flags { + BATADV_UNI_FRAG_HEAD = BIT(0), + BATADV_UNI_FRAG_LARGETAIL = BIT(1), +}; + +/* TT_QUERY subtypes */ +#define BATADV_TT_QUERY_TYPE_MASK 0x3 + +enum batadv_tt_query_packettype { + BATADV_TT_REQUEST = 0, + BATADV_TT_RESPONSE = 1, +}; + +/* TT_QUERY flags */ +enum batadv_tt_query_flags { + BATADV_TT_FULL_TABLE = BIT(2), +}; + +/* BATADV_TT_CLIENT flags. + * Flags from BIT(0) to BIT(7) are sent on the wire, while flags from BIT(8) to + * BIT(15) are used for local computation only + */ +enum batadv_tt_client_flags { + BATADV_TT_CLIENT_DEL = BIT(0), + BATADV_TT_CLIENT_ROAM = BIT(1), + BATADV_TT_CLIENT_WIFI = BIT(2), + BATADV_TT_CLIENT_TEMP = BIT(3), + BATADV_TT_CLIENT_NOPURGE = BIT(8), + BATADV_TT_CLIENT_NEW = BIT(9), + BATADV_TT_CLIENT_PENDING = BIT(10), +}; + +/* claim frame types for the bridge loop avoidance */ +enum batadv_bla_claimframe { + BATADV_CLAIM_TYPE_CLAIM = 0x00, + BATADV_CLAIM_TYPE_UNCLAIM = 0x01, + BATADV_CLAIM_TYPE_ANNOUNCE = 0x02, + BATADV_CLAIM_TYPE_REQUEST = 0x03, +}; + +/* the destination hardware field in the ARP frame is used to + * transport the claim type and the group id + */ +struct batadv_bla_claim_dst { + uint8_t magic[3]; /* FF:43:05 */ + uint8_t type; /* bla_claimframe */ + __be16 group; /* group id */ +}; + +struct batadv_header { + uint8_t packet_type; + uint8_t version; /* batman version field */ + uint8_t ttl; + /* the parent struct has to add a byte after the header to make + * everything 4 bytes aligned again + */ +}; + +struct batadv_ogm_packet { + struct batadv_header header; + uint8_t flags; /* 0x40: DIRECTLINK flag, 0x20 VIS_SERVER flag... */ + __be32 seqno; + uint8_t orig[ETH_ALEN]; + uint8_t prev_sender[ETH_ALEN]; + uint8_t gw_flags; /* flags related to gateway class */ + uint8_t tq; + uint8_t tt_num_changes; + uint8_t ttvn; /* translation table version number */ + __be16 tt_crc; +} __packed; + +#define BATADV_OGM_HLEN sizeof(struct batadv_ogm_packet) + +struct batadv_icmp_packet { + struct batadv_header header; + uint8_t msg_type; /* see ICMP message types above */ + uint8_t dst[ETH_ALEN]; + uint8_t orig[ETH_ALEN]; + __be16 seqno; + uint8_t uid; + uint8_t reserved; +}; + +#define BATADV_RR_LEN 16 + +/* icmp_packet_rr must start with all fields from imcp_packet + * as this is assumed by code that handles ICMP packets + */ +struct batadv_icmp_packet_rr { + struct batadv_header header; + uint8_t msg_type; /* see ICMP message types above */ + uint8_t dst[ETH_ALEN]; + uint8_t orig[ETH_ALEN]; + __be16 seqno; + uint8_t uid; + uint8_t rr_cur; + uint8_t rr[BATADV_RR_LEN][ETH_ALEN]; +}; + +/* All packet headers in front of an ethernet header have to be completely + * divisible by 2 but not by 4 to make the payload after the ethernet + * header again 4 bytes boundary aligned. + * + * A packing of 2 is necessary to avoid extra padding at the end of the struct + * caused by a structure member which is larger than two bytes. Otherwise + * the structure would not fulfill the previously mentioned rule to avoid the + * misalignment of the payload after the ethernet header. It may also lead to + * leakage of information when the padding it not initialized before sending. + */ +#pragma pack(2) + +struct batadv_unicast_packet { + struct batadv_header header; + uint8_t ttvn; /* destination translation table version number */ + uint8_t dest[ETH_ALEN]; + /* "4 bytes boundary + 2 bytes" long to make the payload after the + * following ethernet header again 4 bytes boundary aligned + */ +}; + +/** + * struct batadv_unicast_4addr_packet - extended unicast packet + * @u: common unicast packet header + * @src: address of the source + * @subtype: packet subtype + */ +struct batadv_unicast_4addr_packet { + struct batadv_unicast_packet u; + uint8_t src[ETH_ALEN]; + uint8_t subtype; + uint8_t reserved; + /* "4 bytes boundary + 2 bytes" long to make the payload after the + * following ethernet header again 4 bytes boundary aligned + */ +}; + +struct batadv_unicast_frag_packet { + struct batadv_header header; + uint8_t ttvn; /* destination translation table version number */ + uint8_t dest[ETH_ALEN]; + uint8_t flags; + uint8_t align; + uint8_t orig[ETH_ALEN]; + __be16 seqno; +} __packed; + +struct batadv_bcast_packet { + struct batadv_header header; + uint8_t reserved; + __be32 seqno; + uint8_t orig[ETH_ALEN]; + /* "4 bytes boundary + 2 bytes" long to make the payload after the + * following ethernet header again 4 bytes boundary aligned + */ +}; + +#pragma pack() + +struct batadv_vis_packet { + struct batadv_header header; + uint8_t vis_type; /* which type of vis-participant sent this? */ + __be32 seqno; /* sequence number */ + uint8_t entries; /* number of entries behind this struct */ + uint8_t reserved; + uint8_t vis_orig[ETH_ALEN]; /* originator reporting its neighbors */ + uint8_t target_orig[ETH_ALEN]; /* who should receive this packet */ + uint8_t sender_orig[ETH_ALEN]; /* who sent or forwarded this packet */ +}; + +struct batadv_tt_query_packet { + struct batadv_header header; + /* the flag field is a combination of: + * - TT_REQUEST or TT_RESPONSE + * - TT_FULL_TABLE + */ + uint8_t flags; + uint8_t dst[ETH_ALEN]; + uint8_t src[ETH_ALEN]; + /* the ttvn field is: + * if TT_REQUEST: ttvn that triggered the + * request + * if TT_RESPONSE: new ttvn for the src + * orig_node + */ + uint8_t ttvn; + /* tt_data field is: + * if TT_REQUEST: crc associated with the + * ttvn + * if TT_RESPONSE: table_size + */ + __be16 tt_data; +} __packed; + +struct batadv_roam_adv_packet { + struct batadv_header header; + uint8_t reserved; + uint8_t dst[ETH_ALEN]; + uint8_t src[ETH_ALEN]; + uint8_t client[ETH_ALEN]; +} __packed; + +struct batadv_tt_change { + uint8_t flags; + uint8_t addr[ETH_ALEN]; +} __packed; + +/** + * struct batadv_coded_packet - network coded packet + * @header: common batman packet header and ttl of first included packet + * @reserved: Align following fields to 2-byte boundaries + * @first_source: original source of first included packet + * @first_orig_dest: original destinal of first included packet + * @first_crc: checksum of first included packet + * @first_ttvn: tt-version number of first included packet + * @second_ttl: ttl of second packet + * @second_dest: second receiver of this coded packet + * @second_source: original source of second included packet + * @second_orig_dest: original destination of second included packet + * @second_crc: checksum of second included packet + * @second_ttvn: tt version number of second included packet + * @coded_len: length of network coded part of the payload + */ +struct batadv_coded_packet { + struct batadv_header header; + uint8_t first_ttvn; + /* uint8_t first_dest[ETH_ALEN]; - saved in mac header destination */ + uint8_t first_source[ETH_ALEN]; + uint8_t first_orig_dest[ETH_ALEN]; + __be32 first_crc; + uint8_t second_ttl; + uint8_t second_ttvn; + uint8_t second_dest[ETH_ALEN]; + uint8_t second_source[ETH_ALEN]; + uint8_t second_orig_dest[ETH_ALEN]; + __be32 second_crc; + __be16 coded_len; +}; + +#endif /* _NET_BATMAN_ADV_PACKET_H_ */ diff --git a/net/batman-adv/ring_buffer.c b/net/batman-adv/ring_buffer.c new file mode 100644 index 00000000..ccab0bbd --- /dev/null +++ b/net/batman-adv/ring_buffer.c @@ -0,0 +1,51 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 "ring_buffer.h" + +void batadv_ring_buffer_set(uint8_t lq_recv[], uint8_t *lq_index, + uint8_t value) +{ + lq_recv[*lq_index] = value; + *lq_index = (*lq_index + 1) % BATADV_TQ_GLOBAL_WINDOW_SIZE; +} + +uint8_t batadv_ring_buffer_avg(const uint8_t lq_recv[]) +{ + const uint8_t *ptr; + uint16_t count = 0, i = 0, sum = 0; + + ptr = lq_recv; + + while (i < BATADV_TQ_GLOBAL_WINDOW_SIZE) { + if (*ptr != 0) { + count++; + sum += *ptr; + } + + i++; + ptr++; + } + + if (count == 0) + return 0; + + return (uint8_t)(sum / count); +} diff --git a/net/batman-adv/ring_buffer.h b/net/batman-adv/ring_buffer.h new file mode 100644 index 00000000..3f92ae24 --- /dev/null +++ b/net/batman-adv/ring_buffer.h @@ -0,0 +1,27 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_RING_BUFFER_H_ +#define _NET_BATMAN_ADV_RING_BUFFER_H_ + +void batadv_ring_buffer_set(uint8_t lq_recv[], uint8_t *lq_index, + uint8_t value); +uint8_t batadv_ring_buffer_avg(const uint8_t lq_recv[]); + +#endif /* _NET_BATMAN_ADV_RING_BUFFER_H_ */ diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c new file mode 100644 index 00000000..b27a4d79 --- /dev/null +++ b/net/batman-adv/routing.c @@ -0,0 +1,1300 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 "routing.h" +#include "send.h" +#include "soft-interface.h" +#include "hard-interface.h" +#include "icmp_socket.h" +#include "translation-table.h" +#include "originator.h" +#include "vis.h" +#include "unicast.h" +#include "bridge_loop_avoidance.h" +#include "distributed-arp-table.h" +#include "network-coding.h" + +static int batadv_route_unicast_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); + +void batadv_slide_own_bcast_window(struct batadv_hard_iface *hard_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + unsigned long *word; + uint32_t i; + size_t word_index; + uint8_t *w; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + spin_lock_bh(&orig_node->ogm_cnt_lock); + word_index = hard_iface->if_num * BATADV_NUM_WORDS; + word = &(orig_node->bcast_own[word_index]); + + batadv_bit_get_packet(bat_priv, word, 1, 0); + w = &orig_node->bcast_own_sum[hard_iface->if_num]; + *w = bitmap_weight(word, BATADV_TQ_LOCAL_WINDOW_SIZE); + spin_unlock_bh(&orig_node->ogm_cnt_lock); + } + rcu_read_unlock(); + } +} + +static void _batadv_update_route(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node) +{ + struct batadv_neigh_node *curr_router; + + curr_router = batadv_orig_node_get_router(orig_node); + + /* route deleted */ + if ((curr_router) && (!neigh_node)) { + batadv_dbg(BATADV_DBG_ROUTES, bat_priv, + "Deleting route towards: %pM\n", orig_node->orig); + batadv_tt_global_del_orig(bat_priv, orig_node, + "Deleted route towards originator"); + + /* route added */ + } else if ((!curr_router) && (neigh_node)) { + batadv_dbg(BATADV_DBG_ROUTES, bat_priv, + "Adding route towards: %pM (via %pM)\n", + orig_node->orig, neigh_node->addr); + /* route changed */ + } else if (neigh_node && curr_router) { + batadv_dbg(BATADV_DBG_ROUTES, bat_priv, + "Changing route towards: %pM (now via %pM - was via %pM)\n", + orig_node->orig, neigh_node->addr, + curr_router->addr); + } + + if (curr_router) + batadv_neigh_node_free_ref(curr_router); + + /* increase refcount of new best neighbor */ + if (neigh_node && !atomic_inc_not_zero(&neigh_node->refcount)) + neigh_node = NULL; + + spin_lock_bh(&orig_node->neigh_list_lock); + rcu_assign_pointer(orig_node->router, neigh_node); + spin_unlock_bh(&orig_node->neigh_list_lock); + + /* decrease refcount of previous best neighbor */ + if (curr_router) + batadv_neigh_node_free_ref(curr_router); +} + +void batadv_update_route(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node) +{ + struct batadv_neigh_node *router = NULL; + + if (!orig_node) + goto out; + + router = batadv_orig_node_get_router(orig_node); + + if (router != neigh_node) + _batadv_update_route(bat_priv, orig_node, neigh_node); + +out: + if (router) + batadv_neigh_node_free_ref(router); +} + +/* caller must hold the neigh_list_lock */ +void batadv_bonding_candidate_del(struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node) +{ + /* this neighbor is not part of our candidate list */ + if (list_empty(&neigh_node->bonding_list)) + goto out; + + list_del_rcu(&neigh_node->bonding_list); + INIT_LIST_HEAD(&neigh_node->bonding_list); + batadv_neigh_node_free_ref(neigh_node); + atomic_dec(&orig_node->bond_candidates); + +out: + return; +} + +void batadv_bonding_candidate_add(struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node) +{ + struct batadv_neigh_node *tmp_neigh_node, *router = NULL; + uint8_t interference_candidate = 0; + + spin_lock_bh(&orig_node->neigh_list_lock); + + /* only consider if it has the same primary address ... */ + if (!batadv_compare_eth(orig_node->orig, + neigh_node->orig_node->primary_addr)) + goto candidate_del; + + router = batadv_orig_node_get_router(orig_node); + if (!router) + goto candidate_del; + + /* ... and is good enough to be considered */ + if (neigh_node->tq_avg < router->tq_avg - BATADV_BONDING_TQ_THRESHOLD) + goto candidate_del; + + /* check if we have another candidate with the same mac address or + * interface. If we do, we won't select this candidate because of + * possible interference. + */ + hlist_for_each_entry_rcu(tmp_neigh_node, + &orig_node->neigh_list, list) { + if (tmp_neigh_node == neigh_node) + continue; + + /* we only care if the other candidate is even + * considered as candidate. + */ + if (list_empty(&tmp_neigh_node->bonding_list)) + continue; + + if ((neigh_node->if_incoming == tmp_neigh_node->if_incoming) || + (batadv_compare_eth(neigh_node->addr, + tmp_neigh_node->addr))) { + interference_candidate = 1; + break; + } + } + + /* don't care further if it is an interference candidate */ + if (interference_candidate) + goto candidate_del; + + /* this neighbor already is part of our candidate list */ + if (!list_empty(&neigh_node->bonding_list)) + goto out; + + if (!atomic_inc_not_zero(&neigh_node->refcount)) + goto out; + + list_add_rcu(&neigh_node->bonding_list, &orig_node->bond_list); + atomic_inc(&orig_node->bond_candidates); + goto out; + +candidate_del: + batadv_bonding_candidate_del(orig_node, neigh_node); + +out: + spin_unlock_bh(&orig_node->neigh_list_lock); + + if (router) + batadv_neigh_node_free_ref(router); +} + +/* copy primary address for bonding */ +void +batadv_bonding_save_primary(const struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + const struct batadv_ogm_packet *batman_ogm_packet) +{ + if (!(batman_ogm_packet->flags & BATADV_PRIMARIES_FIRST_HOP)) + return; + + memcpy(orig_neigh_node->primary_addr, orig_node->orig, ETH_ALEN); +} + +/* checks whether the host restarted and is in the protection time. + * returns: + * 0 if the packet is to be accepted + * 1 if the packet is to be ignored. + */ +int batadv_window_protected(struct batadv_priv *bat_priv, int32_t seq_num_diff, + unsigned long *last_reset) +{ + if (seq_num_diff <= -BATADV_TQ_LOCAL_WINDOW_SIZE || + seq_num_diff >= BATADV_EXPECTED_SEQNO_RANGE) { + if (!batadv_has_timed_out(*last_reset, + BATADV_RESET_PROTECTION_MS)) + return 1; + + *last_reset = jiffies; + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "old packet received, start protection\n"); + } + + return 0; +} + +bool batadv_check_management_packet(struct sk_buff *skb, + struct batadv_hard_iface *hard_iface, + int header_len) +{ + struct ethhdr *ethhdr; + + /* drop packet if it has not necessary minimum size */ + if (unlikely(!pskb_may_pull(skb, header_len))) + return false; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + /* packet with broadcast indication but unicast recipient */ + if (!is_broadcast_ether_addr(ethhdr->h_dest)) + return false; + + /* packet with broadcast sender address */ + if (is_broadcast_ether_addr(ethhdr->h_source)) + return false; + + /* create a copy of the skb, if needed, to modify it. */ + if (skb_cow(skb, 0) < 0) + return false; + + /* keep skb linear */ + if (skb_linearize(skb) < 0) + return false; + + return true; +} + +static int batadv_recv_my_icmp_packet(struct batadv_priv *bat_priv, + struct sk_buff *skb, size_t icmp_len) +{ + struct batadv_hard_iface *primary_if = NULL; + struct batadv_orig_node *orig_node = NULL; + struct batadv_icmp_packet_rr *icmp_packet; + int ret = NET_RX_DROP; + + icmp_packet = (struct batadv_icmp_packet_rr *)skb->data; + + /* add data to device queue */ + if (icmp_packet->msg_type != BATADV_ECHO_REQUEST) { + batadv_socket_receive_packet(icmp_packet, icmp_len); + goto out; + } + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + /* answer echo request (ping) */ + /* get routing information */ + orig_node = batadv_orig_hash_find(bat_priv, icmp_packet->orig); + if (!orig_node) + goto out; + + /* create a copy of the skb, if needed, to modify it. */ + if (skb_cow(skb, ETH_HLEN) < 0) + goto out; + + icmp_packet = (struct batadv_icmp_packet_rr *)skb->data; + + memcpy(icmp_packet->dst, icmp_packet->orig, ETH_ALEN); + memcpy(icmp_packet->orig, primary_if->net_dev->dev_addr, ETH_ALEN); + icmp_packet->msg_type = BATADV_ECHO_REPLY; + icmp_packet->header.ttl = BATADV_TTL; + + if (batadv_send_skb_to_orig(skb, orig_node, NULL)) + ret = NET_RX_SUCCESS; + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return ret; +} + +static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + struct batadv_hard_iface *primary_if = NULL; + struct batadv_orig_node *orig_node = NULL; + struct batadv_icmp_packet *icmp_packet; + int ret = NET_RX_DROP; + + icmp_packet = (struct batadv_icmp_packet *)skb->data; + + /* send TTL exceeded if packet is an echo request (traceroute) */ + if (icmp_packet->msg_type != BATADV_ECHO_REQUEST) { + pr_debug("Warning - can't forward icmp packet from %pM to %pM: ttl exceeded\n", + icmp_packet->orig, icmp_packet->dst); + goto out; + } + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + /* get routing information */ + orig_node = batadv_orig_hash_find(bat_priv, icmp_packet->orig); + if (!orig_node) + goto out; + + /* create a copy of the skb, if needed, to modify it. */ + if (skb_cow(skb, ETH_HLEN) < 0) + goto out; + + icmp_packet = (struct batadv_icmp_packet *)skb->data; + + memcpy(icmp_packet->dst, icmp_packet->orig, ETH_ALEN); + memcpy(icmp_packet->orig, primary_if->net_dev->dev_addr, ETH_ALEN); + icmp_packet->msg_type = BATADV_TTL_EXCEEDED; + icmp_packet->header.ttl = BATADV_TTL; + + if (batadv_send_skb_to_orig(skb, orig_node, NULL)) + ret = NET_RX_SUCCESS; + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return ret; +} + + +int batadv_recv_icmp_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_icmp_packet_rr *icmp_packet; + struct ethhdr *ethhdr; + struct batadv_orig_node *orig_node = NULL; + int hdr_size = sizeof(struct batadv_icmp_packet); + int ret = NET_RX_DROP; + + /* we truncate all incoming icmp packets if they don't match our size */ + if (skb->len >= sizeof(struct batadv_icmp_packet_rr)) + hdr_size = sizeof(struct batadv_icmp_packet_rr); + + /* drop packet if it has not necessary minimum size */ + if (unlikely(!pskb_may_pull(skb, hdr_size))) + goto out; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + /* packet with unicast indication but broadcast recipient */ + if (is_broadcast_ether_addr(ethhdr->h_dest)) + goto out; + + /* packet with broadcast sender address */ + if (is_broadcast_ether_addr(ethhdr->h_source)) + goto out; + + /* not for me */ + if (!batadv_is_my_mac(bat_priv, ethhdr->h_dest)) + goto out; + + icmp_packet = (struct batadv_icmp_packet_rr *)skb->data; + + /* add record route information if not full */ + if ((hdr_size == sizeof(struct batadv_icmp_packet_rr)) && + (icmp_packet->rr_cur < BATADV_RR_LEN)) { + memcpy(&(icmp_packet->rr[icmp_packet->rr_cur]), + ethhdr->h_dest, ETH_ALEN); + icmp_packet->rr_cur++; + } + + /* packet for me */ + if (batadv_is_my_mac(bat_priv, icmp_packet->dst)) + return batadv_recv_my_icmp_packet(bat_priv, skb, hdr_size); + + /* TTL exceeded */ + if (icmp_packet->header.ttl < 2) + return batadv_recv_icmp_ttl_exceeded(bat_priv, skb); + + /* get routing information */ + orig_node = batadv_orig_hash_find(bat_priv, icmp_packet->dst); + if (!orig_node) + goto out; + + /* create a copy of the skb, if needed, to modify it. */ + if (skb_cow(skb, ETH_HLEN) < 0) + goto out; + + icmp_packet = (struct batadv_icmp_packet_rr *)skb->data; + + /* decrement ttl */ + icmp_packet->header.ttl--; + + /* route it */ + if (batadv_send_skb_to_orig(skb, orig_node, recv_if)) + ret = NET_RX_SUCCESS; + +out: + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return ret; +} + +/* In the bonding case, send the packets in a round + * robin fashion over the remaining interfaces. + * + * This method rotates the bonding list and increases the + * returned router's refcount. + */ +static struct batadv_neigh_node * +batadv_find_bond_router(struct batadv_orig_node *primary_orig, + const struct batadv_hard_iface *recv_if) +{ + struct batadv_neigh_node *tmp_neigh_node; + struct batadv_neigh_node *router = NULL, *first_candidate = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(tmp_neigh_node, &primary_orig->bond_list, + bonding_list) { + if (!first_candidate) + first_candidate = tmp_neigh_node; + + /* recv_if == NULL on the first node. */ + if (tmp_neigh_node->if_incoming == recv_if) + continue; + + if (!atomic_inc_not_zero(&tmp_neigh_node->refcount)) + continue; + + router = tmp_neigh_node; + break; + } + + /* use the first candidate if nothing was found. */ + if (!router && first_candidate && + atomic_inc_not_zero(&first_candidate->refcount)) + router = first_candidate; + + if (!router) + goto out; + + /* selected should point to the next element + * after the current router + */ + spin_lock_bh(&primary_orig->neigh_list_lock); + /* this is a list_move(), which unfortunately + * does not exist as rcu version + */ + list_del_rcu(&primary_orig->bond_list); + list_add_rcu(&primary_orig->bond_list, + &router->bonding_list); + spin_unlock_bh(&primary_orig->neigh_list_lock); + +out: + rcu_read_unlock(); + return router; +} + +/* Interface Alternating: Use the best of the + * remaining candidates which are not using + * this interface. + * + * Increases the returned router's refcount + */ +static struct batadv_neigh_node * +batadv_find_ifalter_router(struct batadv_orig_node *primary_orig, + const struct batadv_hard_iface *recv_if) +{ + struct batadv_neigh_node *tmp_neigh_node; + struct batadv_neigh_node *router = NULL, *first_candidate = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(tmp_neigh_node, &primary_orig->bond_list, + bonding_list) { + if (!first_candidate) + first_candidate = tmp_neigh_node; + + /* recv_if == NULL on the first node. */ + if (tmp_neigh_node->if_incoming == recv_if) + continue; + + if (router && tmp_neigh_node->tq_avg <= router->tq_avg) + continue; + + if (!atomic_inc_not_zero(&tmp_neigh_node->refcount)) + continue; + + /* decrement refcount of previously selected router */ + if (router) + batadv_neigh_node_free_ref(router); + + /* we found a better router (or at least one valid router) */ + router = tmp_neigh_node; + } + + /* use the first candidate if nothing was found. */ + if (!router && first_candidate && + atomic_inc_not_zero(&first_candidate->refcount)) + router = first_candidate; + + rcu_read_unlock(); + return router; +} + +/** + * batadv_check_unicast_packet - Check for malformed unicast packets + * @bat_priv: the bat priv with all the soft interface information + * @skb: packet to check + * @hdr_size: size of header to pull + * + * Check for short header and bad addresses in given packet. Returns negative + * value when check fails and 0 otherwise. The negative value depends on the + * reason: -ENODATA for bad header, -EBADR for broadcast destination or source, + * and -EREMOTE for non-local (other host) destination. + */ +static int batadv_check_unicast_packet(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_size) +{ + struct ethhdr *ethhdr; + + /* drop packet if it has not necessary minimum size */ + if (unlikely(!pskb_may_pull(skb, hdr_size))) + return -ENODATA; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + /* packet with unicast indication but broadcast recipient */ + if (is_broadcast_ether_addr(ethhdr->h_dest)) + return -EBADR; + + /* packet with broadcast sender address */ + if (is_broadcast_ether_addr(ethhdr->h_source)) + return -EBADR; + + /* not for me */ + if (!batadv_is_my_mac(bat_priv, ethhdr->h_dest)) + return -EREMOTE; + + return 0; +} + +int batadv_recv_tt_query(struct sk_buff *skb, struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_tt_query_packet *tt_query; + uint16_t tt_size; + int hdr_size = sizeof(*tt_query); + char tt_flag; + size_t packet_size; + + if (batadv_check_unicast_packet(bat_priv, skb, hdr_size) < 0) + return NET_RX_DROP; + + /* I could need to modify it */ + if (skb_cow(skb, sizeof(struct batadv_tt_query_packet)) < 0) + goto out; + + tt_query = (struct batadv_tt_query_packet *)skb->data; + + switch (tt_query->flags & BATADV_TT_QUERY_TYPE_MASK) { + case BATADV_TT_REQUEST: + batadv_inc_counter(bat_priv, BATADV_CNT_TT_REQUEST_RX); + + /* If we cannot provide an answer the tt_request is + * forwarded + */ + if (!batadv_send_tt_response(bat_priv, tt_query)) { + if (tt_query->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", + tt_query->dst, + tt_flag); + return batadv_route_unicast_packet(skb, recv_if); + } + break; + case BATADV_TT_RESPONSE: + batadv_inc_counter(bat_priv, BATADV_CNT_TT_RESPONSE_RX); + + if (batadv_is_my_mac(bat_priv, tt_query->dst)) { + /* packet needs to be linearized to access the TT + * changes + */ + if (skb_linearize(skb) < 0) + goto out; + /* skb_linearize() possibly changed skb->data */ + tt_query = (struct batadv_tt_query_packet *)skb->data; + + tt_size = batadv_tt_len(ntohs(tt_query->tt_data)); + + /* Ensure we have all the claimed data */ + packet_size = sizeof(struct batadv_tt_query_packet); + packet_size += tt_size; + if (unlikely(skb_headlen(skb) < packet_size)) + goto out; + + batadv_handle_tt_response(bat_priv, tt_query); + } else { + if (tt_query->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", + tt_query->dst, + tt_flag); + return batadv_route_unicast_packet(skb, recv_if); + } + break; + } + +out: + /* returning NET_RX_DROP will make the caller function kfree the skb */ + return NET_RX_DROP; +} + +int batadv_recv_roam_adv(struct sk_buff *skb, struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_roam_adv_packet *roam_adv_packet; + struct batadv_orig_node *orig_node; + + if (batadv_check_unicast_packet(bat_priv, skb, + sizeof(*roam_adv_packet)) < 0) + goto out; + + batadv_inc_counter(bat_priv, BATADV_CNT_TT_ROAM_ADV_RX); + + roam_adv_packet = (struct batadv_roam_adv_packet *)skb->data; + + if (!batadv_is_my_mac(bat_priv, roam_adv_packet->dst)) + return batadv_route_unicast_packet(skb, recv_if); + + /* check if it is a backbone gateway. we don't accept + * roaming advertisement from it, as it has the same + * entries as we have. + */ + if (batadv_bla_is_backbone_gw_orig(bat_priv, roam_adv_packet->src)) + goto out; + + orig_node = batadv_orig_hash_find(bat_priv, roam_adv_packet->src); + if (!orig_node) + goto out; + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Received ROAMING_ADV from %pM (client %pM)\n", + roam_adv_packet->src, roam_adv_packet->client); + + batadv_tt_global_add(bat_priv, orig_node, roam_adv_packet->client, + BATADV_TT_CLIENT_ROAM, + atomic_read(&orig_node->last_ttvn) + 1); + + batadv_orig_node_free_ref(orig_node); +out: + /* returning NET_RX_DROP will make the caller function kfree the skb */ + return NET_RX_DROP; +} + +/* find a suitable router for this originator, and use + * bonding if possible. increases the found neighbors + * refcount. + */ +struct batadv_neigh_node * +batadv_find_router(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const struct batadv_hard_iface *recv_if) +{ + struct batadv_orig_node *primary_orig_node; + struct batadv_orig_node *router_orig; + struct batadv_neigh_node *router; + static uint8_t zero_mac[ETH_ALEN] = {0, 0, 0, 0, 0, 0}; + int bonding_enabled; + uint8_t *primary_addr; + + if (!orig_node) + return NULL; + + router = batadv_orig_node_get_router(orig_node); + if (!router) + goto err; + + /* without bonding, the first node should + * always choose the default router. + */ + bonding_enabled = atomic_read(&bat_priv->bonding); + + rcu_read_lock(); + /* select default router to output */ + router_orig = router->orig_node; + if (!router_orig) + goto err_unlock; + + if ((!recv_if) && (!bonding_enabled)) + goto return_router; + + primary_addr = router_orig->primary_addr; + + /* if we have something in the primary_addr, we can search + * for a potential bonding candidate. + */ + if (batadv_compare_eth(primary_addr, zero_mac)) + goto return_router; + + /* find the orig_node which has the primary interface. might + * even be the same as our router_orig in many cases + */ + if (batadv_compare_eth(primary_addr, router_orig->orig)) { + primary_orig_node = router_orig; + } else { + primary_orig_node = batadv_orig_hash_find(bat_priv, + primary_addr); + if (!primary_orig_node) + goto return_router; + + batadv_orig_node_free_ref(primary_orig_node); + } + + /* with less than 2 candidates, we can't do any + * bonding and prefer the original router. + */ + if (atomic_read(&primary_orig_node->bond_candidates) < 2) + goto return_router; + + /* all nodes between should choose a candidate which + * is is not on the interface where the packet came + * in. + */ + batadv_neigh_node_free_ref(router); + + if (bonding_enabled) + router = batadv_find_bond_router(primary_orig_node, recv_if); + else + router = batadv_find_ifalter_router(primary_orig_node, recv_if); + +return_router: + if (router && router->if_incoming->if_status != BATADV_IF_ACTIVE) + goto err_unlock; + + rcu_read_unlock(); + return router; +err_unlock: + rcu_read_unlock(); +err: + if (router) + batadv_neigh_node_free_ref(router); + return NULL; +} + +static int batadv_route_unicast_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_orig_node *orig_node = NULL; + struct batadv_neigh_node *neigh_node = NULL; + struct batadv_unicast_packet *unicast_packet; + struct ethhdr *ethhdr = (struct ethhdr *)skb_mac_header(skb); + int ret = NET_RX_DROP; + struct sk_buff *new_skb; + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + + /* TTL exceeded */ + if (unicast_packet->header.ttl < 2) { + pr_debug("Warning - can't forward unicast packet from %pM to %pM: ttl exceeded\n", + ethhdr->h_source, unicast_packet->dest); + goto out; + } + + /* get routing information */ + orig_node = batadv_orig_hash_find(bat_priv, unicast_packet->dest); + + if (!orig_node) + goto out; + + /* find_router() increases neigh_nodes refcount if found. */ + neigh_node = batadv_find_router(bat_priv, orig_node, recv_if); + + if (!neigh_node) + goto out; + + /* create a copy of the skb, if needed, to modify it. */ + if (skb_cow(skb, ETH_HLEN) < 0) + goto out; + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + + if (unicast_packet->header.packet_type == BATADV_UNICAST && + atomic_read(&bat_priv->fragmentation) && + skb->len > neigh_node->if_incoming->net_dev->mtu) { + ret = batadv_frag_send_skb(skb, bat_priv, + neigh_node->if_incoming, + neigh_node->addr); + goto out; + } + + if (unicast_packet->header.packet_type == BATADV_UNICAST_FRAG && + batadv_frag_can_reassemble(skb, + neigh_node->if_incoming->net_dev->mtu)) { + ret = batadv_frag_reassemble_skb(skb, bat_priv, &new_skb); + + if (ret == NET_RX_DROP) + goto out; + + /* packet was buffered for late merge */ + if (!new_skb) { + ret = NET_RX_SUCCESS; + goto out; + } + + skb = new_skb; + unicast_packet = (struct batadv_unicast_packet *)skb->data; + } + + /* decrement ttl */ + unicast_packet->header.ttl--; + + /* network code packet if possible */ + if (batadv_nc_skb_forward(skb, neigh_node, ethhdr)) { + ret = NET_RX_SUCCESS; + } else if (batadv_send_skb_to_orig(skb, orig_node, recv_if)) { + ret = NET_RX_SUCCESS; + + /* Update stats counter */ + batadv_inc_counter(bat_priv, BATADV_CNT_FORWARD); + batadv_add_counter(bat_priv, BATADV_CNT_FORWARD_BYTES, + skb->len + ETH_HLEN); + } + +out: + if (neigh_node) + batadv_neigh_node_free_ref(neigh_node); + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return ret; +} + +/** + * batadv_reroute_unicast_packet - update the unicast header for re-routing + * @bat_priv: the bat priv with all the soft interface information + * @unicast_packet: the unicast header to be updated + * @dst_addr: the payload destination + * + * Search the translation table for dst_addr and update the unicast header with + * the new corresponding information (originator address where the destination + * client currently is and its known TTVN) + * + * Returns true if the packet header has been updated, false otherwise + */ +static bool +batadv_reroute_unicast_packet(struct batadv_priv *bat_priv, + struct batadv_unicast_packet *unicast_packet, + uint8_t *dst_addr) +{ + struct batadv_orig_node *orig_node = NULL; + struct batadv_hard_iface *primary_if = NULL; + bool ret = false; + uint8_t *orig_addr, orig_ttvn; + + if (batadv_is_my_client(bat_priv, dst_addr)) { + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + orig_addr = primary_if->net_dev->dev_addr; + orig_ttvn = (uint8_t)atomic_read(&bat_priv->tt.vn); + } else { + orig_node = batadv_transtable_search(bat_priv, NULL, dst_addr); + if (!orig_node) + goto out; + + if (batadv_compare_eth(orig_node->orig, unicast_packet->dest)) + goto out; + + orig_addr = orig_node->orig; + orig_ttvn = (uint8_t)atomic_read(&orig_node->last_ttvn); + } + + /* update the packet header */ + memcpy(unicast_packet->dest, orig_addr, ETH_ALEN); + unicast_packet->ttvn = orig_ttvn; + + ret = true; +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (orig_node) + batadv_orig_node_free_ref(orig_node); + + return ret; +} + +static int batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, + struct sk_buff *skb, int hdr_len) { + uint8_t curr_ttvn, old_ttvn; + struct batadv_orig_node *orig_node; + struct ethhdr *ethhdr; + struct batadv_hard_iface *primary_if; + struct batadv_unicast_packet *unicast_packet; + int is_old_ttvn; + + /* check if there is enough data before accessing it */ + if (pskb_may_pull(skb, hdr_len + ETH_HLEN) < 0) + return 0; + + /* create a copy of the skb (in case of for re-routing) to modify it. */ + if (skb_cow(skb, sizeof(*unicast_packet)) < 0) + return 0; + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + ethhdr = (struct ethhdr *)(skb->data + hdr_len); + + /* check if the destination client was served by this node and it is now + * roaming. In this case, it means that the node has got a ROAM_ADV + * message and that it knows the new destination in the mesh to re-route + * the packet to + */ + if (batadv_tt_local_client_is_roaming(bat_priv, ethhdr->h_dest)) { + if (batadv_reroute_unicast_packet(bat_priv, unicast_packet, + ethhdr->h_dest)) + net_ratelimited_function(batadv_dbg, BATADV_DBG_TT, + bat_priv, + "Rerouting unicast packet to %pM (dst=%pM): Local Roaming\n", + unicast_packet->dest, + ethhdr->h_dest); + /* at this point the mesh destination should have been + * substituted with the originator address found in the global + * table. If not, let the packet go untouched anyway because + * there is nothing the node can do + */ + return 1; + } + + /* retrieve the TTVN known by this node for the packet destination. This + * value is used later to check if the node which sent (or re-routed + * last time) the packet had an updated information or not + */ + curr_ttvn = (uint8_t)atomic_read(&bat_priv->tt.vn); + if (!batadv_is_my_mac(bat_priv, unicast_packet->dest)) { + orig_node = batadv_orig_hash_find(bat_priv, + unicast_packet->dest); + /* if it is not possible to find the orig_node representing the + * destination, the packet can immediately be dropped as it will + * not be possible to deliver it + */ + if (!orig_node) + return 0; + + curr_ttvn = (uint8_t)atomic_read(&orig_node->last_ttvn); + batadv_orig_node_free_ref(orig_node); + } + + /* check if the TTVN contained in the packet is fresher than what the + * node knows + */ + is_old_ttvn = batadv_seq_before(unicast_packet->ttvn, curr_ttvn); + if (!is_old_ttvn) + return 1; + + old_ttvn = unicast_packet->ttvn; + /* the packet was forged based on outdated network information. Its + * destination can possibly be updated and forwarded towards the new + * target host + */ + if (batadv_reroute_unicast_packet(bat_priv, unicast_packet, + ethhdr->h_dest)) { + net_ratelimited_function(batadv_dbg, BATADV_DBG_TT, bat_priv, + "Rerouting unicast packet to %pM (dst=%pM): TTVN mismatch old_ttvn=%u new_ttvn=%u\n", + unicast_packet->dest, ethhdr->h_dest, + old_ttvn, curr_ttvn); + return 1; + } + + /* the packet has not been re-routed: either the destination is + * currently served by this node or there is no destination at all and + * it is possible to drop the packet + */ + if (!batadv_is_my_client(bat_priv, ethhdr->h_dest)) + return 0; + + /* update the header in order to let the packet be delivered to this + * node's soft interface + */ + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + return 0; + + memcpy(unicast_packet->dest, primary_if->net_dev->dev_addr, ETH_ALEN); + + batadv_hardif_free_ref(primary_if); + + unicast_packet->ttvn = curr_ttvn; + + return 1; +} + +int batadv_recv_unicast_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_unicast_packet *unicast_packet; + struct batadv_unicast_4addr_packet *unicast_4addr_packet; + uint8_t *orig_addr; + struct batadv_orig_node *orig_node = NULL; + int check, hdr_size = sizeof(*unicast_packet); + bool is4addr; + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + unicast_4addr_packet = (struct batadv_unicast_4addr_packet *)skb->data; + + is4addr = unicast_packet->header.packet_type == BATADV_UNICAST_4ADDR; + /* the caller function should have already pulled 2 bytes */ + if (is4addr) + hdr_size = sizeof(*unicast_4addr_packet); + + /* function returns -EREMOTE for promiscuous packets */ + check = batadv_check_unicast_packet(bat_priv, skb, hdr_size); + + /* Even though the packet is not for us, we might save it to use for + * decoding a later received coded packet + */ + if (check == -EREMOTE) + batadv_nc_skb_store_sniffed_unicast(bat_priv, skb); + + if (check < 0) + return NET_RX_DROP; + if (!batadv_check_unicast_ttvn(bat_priv, skb, hdr_size)) + return NET_RX_DROP; + + /* packet for me */ + if (batadv_is_my_mac(bat_priv, unicast_packet->dest)) { + if (is4addr) { + batadv_dat_inc_counter(bat_priv, + unicast_4addr_packet->subtype); + orig_addr = unicast_4addr_packet->src; + orig_node = batadv_orig_hash_find(bat_priv, orig_addr); + } + + if (batadv_dat_snoop_incoming_arp_request(bat_priv, skb, + hdr_size)) + goto rx_success; + if (batadv_dat_snoop_incoming_arp_reply(bat_priv, skb, + hdr_size)) + goto rx_success; + + batadv_interface_rx(recv_if->soft_iface, skb, recv_if, hdr_size, + orig_node); + +rx_success: + if (orig_node) + batadv_orig_node_free_ref(orig_node); + + return NET_RX_SUCCESS; + } + + return batadv_route_unicast_packet(skb, recv_if); +} + +int batadv_recv_ucast_frag_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_unicast_frag_packet *unicast_packet; + int hdr_size = sizeof(*unicast_packet); + struct sk_buff *new_skb = NULL; + int ret; + + if (batadv_check_unicast_packet(bat_priv, skb, hdr_size) < 0) + return NET_RX_DROP; + + if (!batadv_check_unicast_ttvn(bat_priv, skb, hdr_size)) + return NET_RX_DROP; + + unicast_packet = (struct batadv_unicast_frag_packet *)skb->data; + + /* packet for me */ + if (batadv_is_my_mac(bat_priv, unicast_packet->dest)) { + ret = batadv_frag_reassemble_skb(skb, bat_priv, &new_skb); + + if (ret == NET_RX_DROP) + return NET_RX_DROP; + + /* packet was buffered for late merge */ + if (!new_skb) + return NET_RX_SUCCESS; + + if (batadv_dat_snoop_incoming_arp_request(bat_priv, new_skb, + hdr_size)) + goto rx_success; + if (batadv_dat_snoop_incoming_arp_reply(bat_priv, new_skb, + hdr_size)) + goto rx_success; + + batadv_interface_rx(recv_if->soft_iface, new_skb, recv_if, + sizeof(struct batadv_unicast_packet), NULL); + +rx_success: + return NET_RX_SUCCESS; + } + + return batadv_route_unicast_packet(skb, recv_if); +} + + +int batadv_recv_bcast_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + struct batadv_orig_node *orig_node = NULL; + struct batadv_bcast_packet *bcast_packet; + struct ethhdr *ethhdr; + int hdr_size = sizeof(*bcast_packet); + int ret = NET_RX_DROP; + int32_t seq_diff; + + /* drop packet if it has not necessary minimum size */ + if (unlikely(!pskb_may_pull(skb, hdr_size))) + goto out; + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + /* packet with broadcast indication but unicast recipient */ + if (!is_broadcast_ether_addr(ethhdr->h_dest)) + goto out; + + /* packet with broadcast sender address */ + if (is_broadcast_ether_addr(ethhdr->h_source)) + goto out; + + /* ignore broadcasts sent by myself */ + if (batadv_is_my_mac(bat_priv, ethhdr->h_source)) + goto out; + + bcast_packet = (struct batadv_bcast_packet *)skb->data; + + /* ignore broadcasts originated by myself */ + if (batadv_is_my_mac(bat_priv, bcast_packet->orig)) + goto out; + + if (bcast_packet->header.ttl < 2) + goto out; + + orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig); + + if (!orig_node) + goto out; + + spin_lock_bh(&orig_node->bcast_seqno_lock); + + /* check whether the packet is a duplicate */ + if (batadv_test_bit(orig_node->bcast_bits, orig_node->last_bcast_seqno, + ntohl(bcast_packet->seqno))) + goto spin_unlock; + + seq_diff = ntohl(bcast_packet->seqno) - orig_node->last_bcast_seqno; + + /* check whether the packet is old and the host just restarted. */ + if (batadv_window_protected(bat_priv, seq_diff, + &orig_node->bcast_seqno_reset)) + goto spin_unlock; + + /* mark broadcast in flood history, update window position + * if required. + */ + if (batadv_bit_get_packet(bat_priv, orig_node->bcast_bits, seq_diff, 1)) + orig_node->last_bcast_seqno = ntohl(bcast_packet->seqno); + + spin_unlock_bh(&orig_node->bcast_seqno_lock); + + /* check whether this has been sent by another originator before */ + if (batadv_bla_check_bcast_duplist(bat_priv, skb)) + goto out; + + /* rebroadcast packet */ + batadv_add_bcast_packet_to_list(bat_priv, skb, 1); + + /* don't hand the broadcast up if it is from an originator + * from the same backbone. + */ + if (batadv_bla_is_backbone_gw(skb, orig_node, hdr_size)) + goto out; + + if (batadv_dat_snoop_incoming_arp_request(bat_priv, skb, hdr_size)) + goto rx_success; + if (batadv_dat_snoop_incoming_arp_reply(bat_priv, skb, hdr_size)) + goto rx_success; + + /* broadcast for me */ + batadv_interface_rx(recv_if->soft_iface, skb, recv_if, hdr_size, + orig_node); + +rx_success: + ret = NET_RX_SUCCESS; + goto out; + +spin_unlock: + spin_unlock_bh(&orig_node->bcast_seqno_lock); +out: + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return ret; +} + +int batadv_recv_vis_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if) +{ + struct batadv_vis_packet *vis_packet; + struct ethhdr *ethhdr; + struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface); + int hdr_size = sizeof(*vis_packet); + + /* keep skb linear */ + if (skb_linearize(skb) < 0) + return NET_RX_DROP; + + if (unlikely(!pskb_may_pull(skb, hdr_size))) + return NET_RX_DROP; + + vis_packet = (struct batadv_vis_packet *)skb->data; + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + /* not for me */ + if (!batadv_is_my_mac(bat_priv, ethhdr->h_dest)) + return NET_RX_DROP; + + /* ignore own packets */ + if (batadv_is_my_mac(bat_priv, vis_packet->vis_orig)) + return NET_RX_DROP; + + if (batadv_is_my_mac(bat_priv, vis_packet->sender_orig)) + return NET_RX_DROP; + + switch (vis_packet->vis_type) { + case BATADV_VIS_TYPE_SERVER_SYNC: + batadv_receive_server_sync_packet(bat_priv, vis_packet, + skb_headlen(skb)); + break; + + case BATADV_VIS_TYPE_CLIENT_UPDATE: + batadv_receive_client_update_packet(bat_priv, vis_packet, + skb_headlen(skb)); + break; + + default: /* ignore unknown packet */ + break; + } + + /* We take a copy of the data in the packet, so we should + * always free the skbuf. + */ + return NET_RX_DROP; +} diff --git a/net/batman-adv/routing.h b/net/batman-adv/routing.h new file mode 100644 index 00000000..99eeafab --- /dev/null +++ b/net/batman-adv/routing.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_ROUTING_H_ +#define _NET_BATMAN_ADV_ROUTING_H_ + +void batadv_slide_own_bcast_window(struct batadv_hard_iface *hard_iface); +bool batadv_check_management_packet(struct sk_buff *skb, + struct batadv_hard_iface *hard_iface, + int header_len); +void batadv_update_route(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node); +int batadv_recv_icmp_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +int batadv_recv_unicast_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +int batadv_recv_ucast_frag_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +int batadv_recv_bcast_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +int batadv_recv_vis_packet(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +int batadv_recv_tt_query(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +int batadv_recv_roam_adv(struct sk_buff *skb, + struct batadv_hard_iface *recv_if); +struct batadv_neigh_node * +batadv_find_router(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const struct batadv_hard_iface *recv_if); +void batadv_bonding_candidate_del(struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node); +void batadv_bonding_candidate_add(struct batadv_orig_node *orig_node, + struct batadv_neigh_node *neigh_node); +void batadv_bonding_save_primary(const struct batadv_orig_node *orig_node, + struct batadv_orig_node *orig_neigh_node, + const struct batadv_ogm_packet + *batman_ogm_packet); +int batadv_window_protected(struct batadv_priv *bat_priv, int32_t seq_num_diff, + unsigned long *last_reset); + +#endif /* _NET_BATMAN_ADV_ROUTING_H_ */ diff --git a/net/batman-adv/send.c b/net/batman-adv/send.c new file mode 100644 index 00000000..263cfd1c --- /dev/null +++ b/net/batman-adv/send.c @@ -0,0 +1,386 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 "distributed-arp-table.h" +#include "send.h" +#include "routing.h" +#include "translation-table.h" +#include "soft-interface.h" +#include "hard-interface.h" +#include "vis.h" +#include "gateway_common.h" +#include "originator.h" +#include "network-coding.h" + +#include <linux/if_ether.h> + +static void batadv_send_outstanding_bcast_packet(struct work_struct *work); + +/* send out an already prepared packet to the given address via the + * specified batman interface + */ +int batadv_send_skb_packet(struct sk_buff *skb, + struct batadv_hard_iface *hard_iface, + const uint8_t *dst_addr) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + struct ethhdr *ethhdr; + + if (hard_iface->if_status != BATADV_IF_ACTIVE) + goto send_skb_err; + + if (unlikely(!hard_iface->net_dev)) + goto send_skb_err; + + if (!(hard_iface->net_dev->flags & IFF_UP)) { + pr_warn("Interface %s is not up - can't send packet via that interface!\n", + hard_iface->net_dev->name); + goto send_skb_err; + } + + /* push to the ethernet header. */ + if (batadv_skb_head_push(skb, ETH_HLEN) < 0) + goto send_skb_err; + + skb_reset_mac_header(skb); + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + memcpy(ethhdr->h_source, hard_iface->net_dev->dev_addr, ETH_ALEN); + memcpy(ethhdr->h_dest, dst_addr, ETH_ALEN); + ethhdr->h_proto = __constant_htons(ETH_P_BATMAN); + + skb_set_network_header(skb, ETH_HLEN); + skb->priority = TC_PRIO_CONTROL; + skb->protocol = __constant_htons(ETH_P_BATMAN); + + skb->dev = hard_iface->net_dev; + + /* Save a clone of the skb to use when decoding coded packets */ + batadv_nc_skb_store_for_decoding(bat_priv, skb); + + /* dev_queue_xmit() returns a negative result on error. However on + * congestion and traffic shaping, it drops and returns NET_XMIT_DROP + * (which is > 0). This will not be treated as an error. + */ + return dev_queue_xmit(skb); +send_skb_err: + kfree_skb(skb); + return NET_XMIT_DROP; +} + +/** + * batadv_send_skb_to_orig - Lookup next-hop and transmit skb. + * @skb: Packet to be transmitted. + * @orig_node: Final destination of the packet. + * @recv_if: Interface used when receiving the packet (can be NULL). + * + * Looks up the best next-hop towards the passed originator and passes the + * skb on for preparation of MAC header. If the packet originated from this + * host, NULL can be passed as recv_if and no interface alternating is + * attempted. + * + * Returns TRUE on success; FALSE otherwise. + */ +bool batadv_send_skb_to_orig(struct sk_buff *skb, + struct batadv_orig_node *orig_node, + struct batadv_hard_iface *recv_if) +{ + struct batadv_priv *bat_priv = orig_node->bat_priv; + struct batadv_neigh_node *neigh_node; + + /* batadv_find_router() increases neigh_nodes refcount if found. */ + neigh_node = batadv_find_router(bat_priv, orig_node, recv_if); + if (!neigh_node) + return false; + + /* route it */ + batadv_send_skb_packet(skb, neigh_node->if_incoming, neigh_node->addr); + + batadv_neigh_node_free_ref(neigh_node); + + return true; +} + +void batadv_schedule_bat_ogm(struct batadv_hard_iface *hard_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface); + + if ((hard_iface->if_status == BATADV_IF_NOT_IN_USE) || + (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED)) + return; + + /* the interface gets activated here to avoid race conditions between + * the moment of activating the interface in + * hardif_activate_interface() where the originator mac is set and + * outdated packets (especially uninitialized mac addresses) in the + * packet queue + */ + if (hard_iface->if_status == BATADV_IF_TO_BE_ACTIVATED) + hard_iface->if_status = BATADV_IF_ACTIVE; + + bat_priv->bat_algo_ops->bat_ogm_schedule(hard_iface); +} + +static void batadv_forw_packet_free(struct batadv_forw_packet *forw_packet) +{ + if (forw_packet->skb) + kfree_skb(forw_packet->skb); + if (forw_packet->if_incoming) + batadv_hardif_free_ref(forw_packet->if_incoming); + kfree(forw_packet); +} + +static void +_batadv_add_bcast_packet_to_list(struct batadv_priv *bat_priv, + struct batadv_forw_packet *forw_packet, + unsigned long send_time) +{ + INIT_HLIST_NODE(&forw_packet->list); + + /* add new packet to packet list */ + spin_lock_bh(&bat_priv->forw_bcast_list_lock); + hlist_add_head(&forw_packet->list, &bat_priv->forw_bcast_list); + spin_unlock_bh(&bat_priv->forw_bcast_list_lock); + + /* start timer for this packet */ + queue_delayed_work(batadv_event_workqueue, &forw_packet->delayed_work, + send_time); +} + +/* add a broadcast packet to the queue and setup timers. broadcast packets + * are sent multiple times to increase probability for being received. + * + * This function returns NETDEV_TX_OK on success and NETDEV_TX_BUSY on + * errors. + * + * The skb is not consumed, so the caller should make sure that the + * skb is freed. + */ +int batadv_add_bcast_packet_to_list(struct batadv_priv *bat_priv, + const struct sk_buff *skb, + unsigned long delay) +{ + struct batadv_hard_iface *primary_if = NULL; + struct batadv_forw_packet *forw_packet; + struct batadv_bcast_packet *bcast_packet; + struct sk_buff *newskb; + + if (!batadv_atomic_dec_not_zero(&bat_priv->bcast_queue_left)) { + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "bcast packet queue full\n"); + goto out; + } + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out_and_inc; + + forw_packet = kmalloc(sizeof(*forw_packet), GFP_ATOMIC); + + if (!forw_packet) + goto out_and_inc; + + newskb = skb_copy(skb, GFP_ATOMIC); + if (!newskb) + goto packet_free; + + /* as we have a copy now, it is safe to decrease the TTL */ + bcast_packet = (struct batadv_bcast_packet *)newskb->data; + bcast_packet->header.ttl--; + + skb_reset_mac_header(newskb); + + forw_packet->skb = newskb; + forw_packet->if_incoming = primary_if; + + /* how often did we send the bcast packet ? */ + forw_packet->num_packets = 0; + + INIT_DELAYED_WORK(&forw_packet->delayed_work, + batadv_send_outstanding_bcast_packet); + + _batadv_add_bcast_packet_to_list(bat_priv, forw_packet, delay); + return NETDEV_TX_OK; + +packet_free: + kfree(forw_packet); +out_and_inc: + atomic_inc(&bat_priv->bcast_queue_left); +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return NETDEV_TX_BUSY; +} + +static void batadv_send_outstanding_bcast_packet(struct work_struct *work) +{ + struct batadv_hard_iface *hard_iface; + struct delayed_work *delayed_work; + struct batadv_forw_packet *forw_packet; + struct sk_buff *skb1; + struct net_device *soft_iface; + struct batadv_priv *bat_priv; + + delayed_work = container_of(work, struct delayed_work, work); + forw_packet = container_of(delayed_work, struct batadv_forw_packet, + delayed_work); + soft_iface = forw_packet->if_incoming->soft_iface; + bat_priv = netdev_priv(soft_iface); + + spin_lock_bh(&bat_priv->forw_bcast_list_lock); + hlist_del(&forw_packet->list); + spin_unlock_bh(&bat_priv->forw_bcast_list_lock); + + if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING) + goto out; + + if (batadv_dat_drop_broadcast_packet(bat_priv, forw_packet)) + goto out; + + /* rebroadcast packet */ + rcu_read_lock(); + list_for_each_entry_rcu(hard_iface, &batadv_hardif_list, list) { + if (hard_iface->soft_iface != soft_iface) + continue; + + /* send a copy of the saved skb */ + skb1 = skb_clone(forw_packet->skb, GFP_ATOMIC); + if (skb1) + batadv_send_skb_packet(skb1, hard_iface, + batadv_broadcast_addr); + } + rcu_read_unlock(); + + forw_packet->num_packets++; + + /* if we still have some more bcasts to send */ + if (forw_packet->num_packets < 3) { + _batadv_add_bcast_packet_to_list(bat_priv, forw_packet, + msecs_to_jiffies(5)); + return; + } + +out: + batadv_forw_packet_free(forw_packet); + atomic_inc(&bat_priv->bcast_queue_left); +} + +void batadv_send_outstanding_bat_ogm_packet(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct batadv_forw_packet *forw_packet; + struct batadv_priv *bat_priv; + + delayed_work = container_of(work, struct delayed_work, work); + forw_packet = container_of(delayed_work, struct batadv_forw_packet, + delayed_work); + bat_priv = netdev_priv(forw_packet->if_incoming->soft_iface); + spin_lock_bh(&bat_priv->forw_bat_list_lock); + hlist_del(&forw_packet->list); + spin_unlock_bh(&bat_priv->forw_bat_list_lock); + + if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_DEACTIVATING) + goto out; + + bat_priv->bat_algo_ops->bat_ogm_emit(forw_packet); + + /* we have to have at least one packet in the queue + * to determine the queues wake up time unless we are + * shutting down + */ + if (forw_packet->own) + batadv_schedule_bat_ogm(forw_packet->if_incoming); + +out: + /* don't count own packet */ + if (!forw_packet->own) + atomic_inc(&bat_priv->batman_queue_left); + + batadv_forw_packet_free(forw_packet); +} + +void +batadv_purge_outstanding_packets(struct batadv_priv *bat_priv, + const struct batadv_hard_iface *hard_iface) +{ + struct batadv_forw_packet *forw_packet; + struct hlist_node *safe_tmp_node; + bool pending; + + if (hard_iface) + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "purge_outstanding_packets(): %s\n", + hard_iface->net_dev->name); + else + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "purge_outstanding_packets()\n"); + + /* free bcast list */ + spin_lock_bh(&bat_priv->forw_bcast_list_lock); + hlist_for_each_entry_safe(forw_packet, safe_tmp_node, + &bat_priv->forw_bcast_list, list) { + /* if purge_outstanding_packets() was called with an argument + * we delete only packets belonging to the given interface + */ + if ((hard_iface) && + (forw_packet->if_incoming != hard_iface)) + continue; + + spin_unlock_bh(&bat_priv->forw_bcast_list_lock); + + /* batadv_send_outstanding_bcast_packet() will lock the list to + * delete the item from the list + */ + pending = cancel_delayed_work_sync(&forw_packet->delayed_work); + spin_lock_bh(&bat_priv->forw_bcast_list_lock); + + if (pending) { + hlist_del(&forw_packet->list); + batadv_forw_packet_free(forw_packet); + } + } + spin_unlock_bh(&bat_priv->forw_bcast_list_lock); + + /* free batman packet list */ + spin_lock_bh(&bat_priv->forw_bat_list_lock); + hlist_for_each_entry_safe(forw_packet, safe_tmp_node, + &bat_priv->forw_bat_list, list) { + /* if purge_outstanding_packets() was called with an argument + * we delete only packets belonging to the given interface + */ + if ((hard_iface) && + (forw_packet->if_incoming != hard_iface)) + continue; + + spin_unlock_bh(&bat_priv->forw_bat_list_lock); + + /* send_outstanding_bat_packet() will lock the list to + * delete the item from the list + */ + pending = cancel_delayed_work_sync(&forw_packet->delayed_work); + spin_lock_bh(&bat_priv->forw_bat_list_lock); + + if (pending) { + hlist_del(&forw_packet->list); + batadv_forw_packet_free(forw_packet); + } + } + spin_unlock_bh(&bat_priv->forw_bat_list_lock); +} diff --git a/net/batman-adv/send.h b/net/batman-adv/send.h new file mode 100644 index 00000000..38e662f6 --- /dev/null +++ b/net/batman-adv/send.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_SEND_H_ +#define _NET_BATMAN_ADV_SEND_H_ + +int batadv_send_skb_packet(struct sk_buff *skb, + struct batadv_hard_iface *hard_iface, + const uint8_t *dst_addr); +bool batadv_send_skb_to_orig(struct sk_buff *skb, + struct batadv_orig_node *orig_node, + struct batadv_hard_iface *recv_if); +void batadv_schedule_bat_ogm(struct batadv_hard_iface *hard_iface); +int batadv_add_bcast_packet_to_list(struct batadv_priv *bat_priv, + const struct sk_buff *skb, + unsigned long delay); +void batadv_send_outstanding_bat_ogm_packet(struct work_struct *work); +void +batadv_purge_outstanding_packets(struct batadv_priv *bat_priv, + const struct batadv_hard_iface *hard_iface); + +#endif /* _NET_BATMAN_ADV_SEND_H_ */ diff --git a/net/batman-adv/soft-interface.c b/net/batman-adv/soft-interface.c new file mode 100644 index 00000000..819dfb00 --- /dev/null +++ b/net/batman-adv/soft-interface.c @@ -0,0 +1,803 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 "soft-interface.h" +#include "hard-interface.h" +#include "distributed-arp-table.h" +#include "routing.h" +#include "send.h" +#include "debugfs.h" +#include "translation-table.h" +#include "hash.h" +#include "gateway_common.h" +#include "gateway_client.h" +#include "sysfs.h" +#include "originator.h" +#include <linux/slab.h> +#include <linux/ethtool.h> +#include <linux/etherdevice.h> +#include <linux/if_vlan.h> +#include <linux/if_ether.h> +#include "unicast.h" +#include "bridge_loop_avoidance.h" +#include "network-coding.h" + + +static int batadv_get_settings(struct net_device *dev, struct ethtool_cmd *cmd); +static void batadv_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info); +static u32 batadv_get_msglevel(struct net_device *dev); +static void batadv_set_msglevel(struct net_device *dev, u32 value); +static u32 batadv_get_link(struct net_device *dev); +static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data); +static void batadv_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data); +static int batadv_get_sset_count(struct net_device *dev, int stringset); + +static const struct ethtool_ops batadv_ethtool_ops = { + .get_settings = batadv_get_settings, + .get_drvinfo = batadv_get_drvinfo, + .get_msglevel = batadv_get_msglevel, + .set_msglevel = batadv_set_msglevel, + .get_link = batadv_get_link, + .get_strings = batadv_get_strings, + .get_ethtool_stats = batadv_get_ethtool_stats, + .get_sset_count = batadv_get_sset_count, +}; + +int batadv_skb_head_push(struct sk_buff *skb, unsigned int len) +{ + int result; + + /* TODO: We must check if we can release all references to non-payload + * data using skb_header_release in our skbs to allow skb_cow_header to + * work optimally. This means that those skbs are not allowed to read + * or write any data which is before the current position of skb->data + * after that call and thus allow other skbs with the same data buffer + * to write freely in that area. + */ + result = skb_cow_head(skb, len); + if (result < 0) + return result; + + skb_push(skb, len); + return 0; +} + +static int batadv_interface_open(struct net_device *dev) +{ + netif_start_queue(dev); + return 0; +} + +static int batadv_interface_release(struct net_device *dev) +{ + netif_stop_queue(dev); + return 0; +} + +static struct net_device_stats *batadv_interface_stats(struct net_device *dev) +{ + struct batadv_priv *bat_priv = netdev_priv(dev); + struct net_device_stats *stats = &bat_priv->stats; + + stats->tx_packets = batadv_sum_counter(bat_priv, BATADV_CNT_TX); + stats->tx_bytes = batadv_sum_counter(bat_priv, BATADV_CNT_TX_BYTES); + stats->tx_dropped = batadv_sum_counter(bat_priv, BATADV_CNT_TX_DROPPED); + stats->rx_packets = batadv_sum_counter(bat_priv, BATADV_CNT_RX); + stats->rx_bytes = batadv_sum_counter(bat_priv, BATADV_CNT_RX_BYTES); + return stats; +} + +static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) +{ + struct batadv_priv *bat_priv = netdev_priv(dev); + struct sockaddr *addr = p; + uint8_t old_addr[ETH_ALEN]; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + memcpy(old_addr, dev->dev_addr, ETH_ALEN); + memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); + + /* only modify transtable if it has been initialized before */ + if (atomic_read(&bat_priv->mesh_state) == BATADV_MESH_ACTIVE) { + batadv_tt_local_remove(bat_priv, old_addr, + "mac address changed", false); + batadv_tt_local_add(dev, addr->sa_data, BATADV_NULL_IFINDEX); + } + + return 0; +} + +static int batadv_interface_change_mtu(struct net_device *dev, int new_mtu) +{ + /* check ranges */ + if ((new_mtu < 68) || (new_mtu > batadv_hardif_min_mtu(dev))) + return -EINVAL; + + dev->mtu = new_mtu; + + return 0; +} + +static int batadv_interface_tx(struct sk_buff *skb, + struct net_device *soft_iface) +{ + struct ethhdr *ethhdr = (struct ethhdr *)skb->data; + struct batadv_priv *bat_priv = netdev_priv(soft_iface); + struct batadv_hard_iface *primary_if = NULL; + struct batadv_bcast_packet *bcast_packet; + struct vlan_ethhdr *vhdr; + __be16 ethertype = __constant_htons(ETH_P_BATMAN); + static const uint8_t stp_addr[ETH_ALEN] = {0x01, 0x80, 0xC2, 0x00, + 0x00, 0x00}; + static const uint8_t ectp_addr[ETH_ALEN] = {0xCF, 0x00, 0x00, 0x00, + 0x00, 0x00}; + unsigned int header_len = 0; + int data_len = skb->len, ret; + short vid __maybe_unused = -1; + bool do_bcast = false; + uint32_t seqno; + unsigned long brd_delay = 1; + + if (atomic_read(&bat_priv->mesh_state) != BATADV_MESH_ACTIVE) + goto dropped; + + soft_iface->trans_start = jiffies; + + switch (ntohs(ethhdr->h_proto)) { + case ETH_P_8021Q: + vhdr = (struct vlan_ethhdr *)skb->data; + vid = ntohs(vhdr->h_vlan_TCI) & VLAN_VID_MASK; + + if (vhdr->h_vlan_encapsulated_proto != ethertype) + break; + + /* fall through */ + case ETH_P_BATMAN: + goto dropped; + } + + if (batadv_bla_tx(bat_priv, skb, vid)) + goto dropped; + + /* Register the client MAC in the transtable */ + if (!is_multicast_ether_addr(ethhdr->h_source)) + batadv_tt_local_add(soft_iface, ethhdr->h_source, skb->skb_iif); + + /* don't accept stp packets. STP does not help in meshes. + * better use the bridge loop avoidance ... + * + * The same goes for ECTP sent at least by some Cisco Switches, + * it might confuse the mesh when used with bridge loop avoidance. + */ + if (batadv_compare_eth(ethhdr->h_dest, stp_addr)) + goto dropped; + + if (batadv_compare_eth(ethhdr->h_dest, ectp_addr)) + goto dropped; + + if (is_multicast_ether_addr(ethhdr->h_dest)) { + do_bcast = true; + + switch (atomic_read(&bat_priv->gw_mode)) { + case BATADV_GW_MODE_SERVER: + /* gateway servers should not send dhcp + * requests into the mesh + */ + ret = batadv_gw_is_dhcp_target(skb, &header_len); + if (ret) + goto dropped; + break; + case BATADV_GW_MODE_CLIENT: + /* gateway clients should send dhcp requests + * via unicast to their gateway + */ + ret = batadv_gw_is_dhcp_target(skb, &header_len); + if (ret) + do_bcast = false; + break; + case BATADV_GW_MODE_OFF: + default: + break; + } + } + + /* ethernet packet should be broadcasted */ + if (do_bcast) { + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto dropped; + + /* in case of ARP request, we do not immediately broadcasti the + * packet, instead we first wait for DAT to try to retrieve the + * correct ARP entry + */ + if (batadv_dat_snoop_outgoing_arp_request(bat_priv, skb)) + brd_delay = msecs_to_jiffies(ARP_REQ_DELAY); + + if (batadv_skb_head_push(skb, sizeof(*bcast_packet)) < 0) + goto dropped; + + bcast_packet = (struct batadv_bcast_packet *)skb->data; + bcast_packet->header.version = BATADV_COMPAT_VERSION; + bcast_packet->header.ttl = BATADV_TTL; + + /* batman packet type: broadcast */ + bcast_packet->header.packet_type = BATADV_BCAST; + bcast_packet->reserved = 0; + + /* hw address of first interface is the orig mac because only + * this mac is known throughout the mesh + */ + memcpy(bcast_packet->orig, + primary_if->net_dev->dev_addr, ETH_ALEN); + + /* set broadcast sequence number */ + seqno = atomic_inc_return(&bat_priv->bcast_seqno); + bcast_packet->seqno = htonl(seqno); + + batadv_add_bcast_packet_to_list(bat_priv, skb, brd_delay); + + /* a copy is stored in the bcast list, therefore removing + * the original skb. + */ + kfree_skb(skb); + + /* unicast packet */ + } else { + if (atomic_read(&bat_priv->gw_mode) != BATADV_GW_MODE_OFF) { + ret = batadv_gw_out_of_range(bat_priv, skb, ethhdr); + if (ret) + goto dropped; + } + + if (batadv_dat_snoop_outgoing_arp_request(bat_priv, skb)) + goto dropped; + + batadv_dat_snoop_outgoing_arp_reply(bat_priv, skb); + + ret = batadv_unicast_send_skb(bat_priv, skb); + if (ret != 0) + goto dropped_freed; + } + + batadv_inc_counter(bat_priv, BATADV_CNT_TX); + batadv_add_counter(bat_priv, BATADV_CNT_TX_BYTES, data_len); + goto end; + +dropped: + kfree_skb(skb); +dropped_freed: + batadv_inc_counter(bat_priv, BATADV_CNT_TX_DROPPED); +end: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return NETDEV_TX_OK; +} + +void batadv_interface_rx(struct net_device *soft_iface, + struct sk_buff *skb, struct batadv_hard_iface *recv_if, + int hdr_size, struct batadv_orig_node *orig_node) +{ + struct batadv_priv *bat_priv = netdev_priv(soft_iface); + struct ethhdr *ethhdr; + struct vlan_ethhdr *vhdr; + struct batadv_header *batadv_header = (struct batadv_header *)skb->data; + short vid __maybe_unused = -1; + __be16 ethertype = __constant_htons(ETH_P_BATMAN); + bool is_bcast; + + is_bcast = (batadv_header->packet_type == BATADV_BCAST); + + /* check if enough space is available for pulling, and pull */ + if (!pskb_may_pull(skb, hdr_size)) + goto dropped; + + skb_pull_rcsum(skb, hdr_size); + skb_reset_mac_header(skb); + + ethhdr = (struct ethhdr *)skb_mac_header(skb); + + switch (ntohs(ethhdr->h_proto)) { + case ETH_P_8021Q: + vhdr = (struct vlan_ethhdr *)skb->data; + vid = ntohs(vhdr->h_vlan_TCI) & VLAN_VID_MASK; + + if (vhdr->h_vlan_encapsulated_proto != ethertype) + break; + + /* fall through */ + case ETH_P_BATMAN: + goto dropped; + } + + /* skb->dev & skb->pkt_type are set here */ + if (unlikely(!pskb_may_pull(skb, ETH_HLEN))) + goto dropped; + skb->protocol = eth_type_trans(skb, soft_iface); + + /* should not be necessary anymore as we use skb_pull_rcsum() + * TODO: please verify this and remove this TODO + * -- Dec 21st 2009, Simon Wunderlich + */ + + /* skb->ip_summed = CHECKSUM_UNNECESSARY; */ + + batadv_inc_counter(bat_priv, BATADV_CNT_RX); + batadv_add_counter(bat_priv, BATADV_CNT_RX_BYTES, + skb->len + ETH_HLEN); + + soft_iface->last_rx = jiffies; + + /* Let the bridge loop avoidance check the packet. If will + * not handle it, we can safely push it up. + */ + if (batadv_bla_rx(bat_priv, skb, vid, is_bcast)) + goto out; + + if (orig_node) + batadv_tt_add_temporary_global_entry(bat_priv, orig_node, + ethhdr->h_source); + + if (batadv_is_ap_isolated(bat_priv, ethhdr->h_source, ethhdr->h_dest)) + goto dropped; + + netif_rx(skb); + goto out; + +dropped: + kfree_skb(skb); +out: + return; +} + +/* batman-adv network devices have devices nesting below it and are a special + * "super class" of normal network devices; split their locks off into a + * separate class since they always nest. + */ +static struct lock_class_key batadv_netdev_xmit_lock_key; +static struct lock_class_key batadv_netdev_addr_lock_key; + +/** + * batadv_set_lockdep_class_one - Set lockdep class for a single tx queue + * @dev: device which owns the tx queue + * @txq: tx queue to modify + * @_unused: always NULL + */ +static void batadv_set_lockdep_class_one(struct net_device *dev, + struct netdev_queue *txq, + void *_unused) +{ + lockdep_set_class(&txq->_xmit_lock, &batadv_netdev_xmit_lock_key); +} + +/** + * batadv_set_lockdep_class - Set txq and addr_list lockdep class + * @dev: network device to modify + */ +static void batadv_set_lockdep_class(struct net_device *dev) +{ + lockdep_set_class(&dev->addr_list_lock, &batadv_netdev_addr_lock_key); + netdev_for_each_tx_queue(dev, batadv_set_lockdep_class_one, NULL); +} + +/** + * batadv_softif_destroy_finish - cleans up the remains of a softif + * @work: work queue item + * + * Free the parts of the soft interface which can not be removed under + * rtnl lock (to prevent deadlock situations). + */ +static void batadv_softif_destroy_finish(struct work_struct *work) +{ + struct batadv_priv *bat_priv; + struct net_device *soft_iface; + + bat_priv = container_of(work, struct batadv_priv, + cleanup_work); + soft_iface = bat_priv->soft_iface; + + batadv_sysfs_del_meshif(soft_iface); + + rtnl_lock(); + unregister_netdevice(soft_iface); + rtnl_unlock(); +} + +/** + * batadv_softif_init_late - late stage initialization of soft interface + * @dev: registered network device to modify + * + * Returns error code on failures + */ +static int batadv_softif_init_late(struct net_device *dev) +{ + struct batadv_priv *bat_priv; + int ret; + size_t cnt_len = sizeof(uint64_t) * BATADV_CNT_NUM; + + batadv_set_lockdep_class(dev); + + bat_priv = netdev_priv(dev); + bat_priv->soft_iface = dev; + INIT_WORK(&bat_priv->cleanup_work, batadv_softif_destroy_finish); + + /* batadv_interface_stats() needs to be available as soon as + * register_netdevice() has been called + */ + bat_priv->bat_counters = __alloc_percpu(cnt_len, __alignof__(uint64_t)); + if (!bat_priv->bat_counters) + return -ENOMEM; + + atomic_set(&bat_priv->aggregated_ogms, 1); + atomic_set(&bat_priv->bonding, 0); +#ifdef CONFIG_BATMAN_ADV_BLA + atomic_set(&bat_priv->bridge_loop_avoidance, 0); +#endif +#ifdef CONFIG_BATMAN_ADV_DAT + atomic_set(&bat_priv->distributed_arp_table, 1); +#endif + atomic_set(&bat_priv->ap_isolation, 0); + atomic_set(&bat_priv->vis_mode, BATADV_VIS_TYPE_CLIENT_UPDATE); + atomic_set(&bat_priv->gw_mode, BATADV_GW_MODE_OFF); + atomic_set(&bat_priv->gw_sel_class, 20); + atomic_set(&bat_priv->gw_bandwidth, 41); + atomic_set(&bat_priv->orig_interval, 1000); + atomic_set(&bat_priv->hop_penalty, 30); +#ifdef CONFIG_BATMAN_ADV_DEBUG + atomic_set(&bat_priv->log_level, 0); +#endif + atomic_set(&bat_priv->fragmentation, 1); + atomic_set(&bat_priv->bcast_queue_left, BATADV_BCAST_QUEUE_LEN); + atomic_set(&bat_priv->batman_queue_left, BATADV_BATMAN_QUEUE_LEN); + + atomic_set(&bat_priv->mesh_state, BATADV_MESH_INACTIVE); + atomic_set(&bat_priv->bcast_seqno, 1); + atomic_set(&bat_priv->tt.vn, 0); + atomic_set(&bat_priv->tt.local_changes, 0); + atomic_set(&bat_priv->tt.ogm_append_cnt, 0); +#ifdef CONFIG_BATMAN_ADV_BLA + atomic_set(&bat_priv->bla.num_requests, 0); +#endif + bat_priv->tt.last_changeset = NULL; + bat_priv->tt.last_changeset_len = 0; + + bat_priv->primary_if = NULL; + bat_priv->num_ifaces = 0; + + batadv_nc_init_bat_priv(bat_priv); + + ret = batadv_algo_select(bat_priv, batadv_routing_algo); + if (ret < 0) + goto free_bat_counters; + + ret = batadv_debugfs_add_meshif(dev); + if (ret < 0) + goto free_bat_counters; + + ret = batadv_mesh_init(dev); + if (ret < 0) + goto unreg_debugfs; + + return 0; + +unreg_debugfs: + batadv_debugfs_del_meshif(dev); +free_bat_counters: + free_percpu(bat_priv->bat_counters); + bat_priv->bat_counters = NULL; + + return ret; +} + +/** + * batadv_softif_slave_add - Add a slave interface to a batadv_soft_interface + * @dev: batadv_soft_interface used as master interface + * @slave_dev: net_device which should become the slave interface + * + * Return 0 if successful or error otherwise. + */ +static int batadv_softif_slave_add(struct net_device *dev, + struct net_device *slave_dev) +{ + struct batadv_hard_iface *hard_iface; + int ret = -EINVAL; + + hard_iface = batadv_hardif_get_by_netdev(slave_dev); + if (!hard_iface || hard_iface->soft_iface != NULL) + goto out; + + ret = batadv_hardif_enable_interface(hard_iface, dev->name); + +out: + if (hard_iface) + batadv_hardif_free_ref(hard_iface); + return ret; +} + +/** + * batadv_softif_slave_del - Delete a slave iface from a batadv_soft_interface + * @dev: batadv_soft_interface used as master interface + * @slave_dev: net_device which should be removed from the master interface + * + * Return 0 if successful or error otherwise. + */ +static int batadv_softif_slave_del(struct net_device *dev, + struct net_device *slave_dev) +{ + struct batadv_hard_iface *hard_iface; + int ret = -EINVAL; + + hard_iface = batadv_hardif_get_by_netdev(slave_dev); + + if (!hard_iface || hard_iface->soft_iface != dev) + goto out; + + batadv_hardif_disable_interface(hard_iface, BATADV_IF_CLEANUP_KEEP); + ret = 0; + +out: + if (hard_iface) + batadv_hardif_free_ref(hard_iface); + return ret; +} + +static const struct net_device_ops batadv_netdev_ops = { + .ndo_init = batadv_softif_init_late, + .ndo_open = batadv_interface_open, + .ndo_stop = batadv_interface_release, + .ndo_get_stats = batadv_interface_stats, + .ndo_set_mac_address = batadv_interface_set_mac_addr, + .ndo_change_mtu = batadv_interface_change_mtu, + .ndo_start_xmit = batadv_interface_tx, + .ndo_validate_addr = eth_validate_addr, + .ndo_add_slave = batadv_softif_slave_add, + .ndo_del_slave = batadv_softif_slave_del, +}; + +/** + * batadv_softif_free - Deconstructor of batadv_soft_interface + * @dev: Device to cleanup and remove + */ +static void batadv_softif_free(struct net_device *dev) +{ + batadv_debugfs_del_meshif(dev); + batadv_mesh_free(dev); + + /* some scheduled RCU callbacks need the bat_priv struct to accomplish + * their tasks. Wait for them all to be finished before freeing the + * netdev and its private data (bat_priv) + */ + rcu_barrier(); + + free_netdev(dev); +} + +/** + * batadv_softif_init_early - early stage initialization of soft interface + * @dev: registered network device to modify + */ +static void batadv_softif_init_early(struct net_device *dev) +{ + struct batadv_priv *priv = netdev_priv(dev); + + ether_setup(dev); + + dev->netdev_ops = &batadv_netdev_ops; + dev->destructor = batadv_softif_free; + dev->tx_queue_len = 0; + + /* can't call min_mtu, because the needed variables + * have not been initialized yet + */ + dev->mtu = ETH_DATA_LEN; + /* reserve more space in the skbuff for our header */ + dev->hard_header_len = BATADV_HEADER_LEN; + + /* generate random address */ + eth_hw_addr_random(dev); + + SET_ETHTOOL_OPS(dev, &batadv_ethtool_ops); + + memset(priv, 0, sizeof(*priv)); +} + +struct net_device *batadv_softif_create(const char *name) +{ + struct net_device *soft_iface; + int ret; + + soft_iface = alloc_netdev(sizeof(struct batadv_priv), name, + batadv_softif_init_early); + if (!soft_iface) + return NULL; + + soft_iface->rtnl_link_ops = &batadv_link_ops; + + ret = register_netdevice(soft_iface); + if (ret < 0) { + pr_err("Unable to register the batman interface '%s': %i\n", + name, ret); + free_netdev(soft_iface); + return NULL; + } + + return soft_iface; +} + +/** + * batadv_softif_destroy_sysfs - deletion of batadv_soft_interface via sysfs + * @soft_iface: the to-be-removed batman-adv interface + */ +void batadv_softif_destroy_sysfs(struct net_device *soft_iface) +{ + struct batadv_priv *bat_priv = netdev_priv(soft_iface); + + queue_work(batadv_event_workqueue, &bat_priv->cleanup_work); +} + +/** + * batadv_softif_destroy_netlink - deletion of batadv_soft_interface via netlink + * @soft_iface: the to-be-removed batman-adv interface + * @head: list pointer + */ +static void batadv_softif_destroy_netlink(struct net_device *soft_iface, + struct list_head *head) +{ + struct batadv_hard_iface *hard_iface; + + list_for_each_entry(hard_iface, &batadv_hardif_list, list) { + if (hard_iface->soft_iface == soft_iface) + batadv_hardif_disable_interface(hard_iface, + BATADV_IF_CLEANUP_KEEP); + } + + batadv_sysfs_del_meshif(soft_iface); + unregister_netdevice_queue(soft_iface, head); +} + +int batadv_softif_is_valid(const struct net_device *net_dev) +{ + if (net_dev->netdev_ops->ndo_start_xmit == batadv_interface_tx) + return 1; + + return 0; +} + +struct rtnl_link_ops batadv_link_ops __read_mostly = { + .kind = "batadv", + .priv_size = sizeof(struct batadv_priv), + .setup = batadv_softif_init_early, + .dellink = batadv_softif_destroy_netlink, +}; + +/* ethtool */ +static int batadv_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + cmd->supported = 0; + cmd->advertising = 0; + ethtool_cmd_speed_set(cmd, SPEED_10); + cmd->duplex = DUPLEX_FULL; + cmd->port = PORT_TP; + cmd->phy_address = 0; + cmd->transceiver = XCVR_INTERNAL; + cmd->autoneg = AUTONEG_DISABLE; + cmd->maxtxpkt = 0; + cmd->maxrxpkt = 0; + + return 0; +} + +static void batadv_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + strlcpy(info->driver, "B.A.T.M.A.N. advanced", sizeof(info->driver)); + strlcpy(info->version, BATADV_SOURCE_VERSION, sizeof(info->version)); + strlcpy(info->fw_version, "N/A", sizeof(info->fw_version)); + strlcpy(info->bus_info, "batman", sizeof(info->bus_info)); +} + +static u32 batadv_get_msglevel(struct net_device *dev) +{ + return -EOPNOTSUPP; +} + +static void batadv_set_msglevel(struct net_device *dev, u32 value) +{ +} + +static u32 batadv_get_link(struct net_device *dev) +{ + return 1; +} + +/* Inspired by drivers/net/ethernet/dlink/sundance.c:1702 + * Declare each description string in struct.name[] to get fixed sized buffer + * and compile time checking for strings longer than ETH_GSTRING_LEN. + */ +static const struct { + const char name[ETH_GSTRING_LEN]; +} batadv_counters_strings[] = { + { "tx" }, + { "tx_bytes" }, + { "tx_dropped" }, + { "rx" }, + { "rx_bytes" }, + { "forward" }, + { "forward_bytes" }, + { "mgmt_tx" }, + { "mgmt_tx_bytes" }, + { "mgmt_rx" }, + { "mgmt_rx_bytes" }, + { "tt_request_tx" }, + { "tt_request_rx" }, + { "tt_response_tx" }, + { "tt_response_rx" }, + { "tt_roam_adv_tx" }, + { "tt_roam_adv_rx" }, +#ifdef CONFIG_BATMAN_ADV_DAT + { "dat_get_tx" }, + { "dat_get_rx" }, + { "dat_put_tx" }, + { "dat_put_rx" }, + { "dat_cached_reply_tx" }, +#endif +#ifdef CONFIG_BATMAN_ADV_NC + { "nc_code" }, + { "nc_code_bytes" }, + { "nc_recode" }, + { "nc_recode_bytes" }, + { "nc_buffer" }, + { "nc_decode" }, + { "nc_decode_bytes" }, + { "nc_decode_failed" }, + { "nc_sniffed" }, +#endif +}; + +static void batadv_get_strings(struct net_device *dev, uint32_t stringset, + uint8_t *data) +{ + if (stringset == ETH_SS_STATS) + memcpy(data, batadv_counters_strings, + sizeof(batadv_counters_strings)); +} + +static void batadv_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, + uint64_t *data) +{ + struct batadv_priv *bat_priv = netdev_priv(dev); + int i; + + for (i = 0; i < BATADV_CNT_NUM; i++) + data[i] = batadv_sum_counter(bat_priv, i); +} + +static int batadv_get_sset_count(struct net_device *dev, int stringset) +{ + if (stringset == ETH_SS_STATS) + return BATADV_CNT_NUM; + + return -EOPNOTSUPP; +} diff --git a/net/batman-adv/soft-interface.h b/net/batman-adv/soft-interface.h new file mode 100644 index 00000000..2f2472c2 --- /dev/null +++ b/net/batman-adv/soft-interface.h @@ -0,0 +1,32 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_SOFT_INTERFACE_H_ +#define _NET_BATMAN_ADV_SOFT_INTERFACE_H_ + +int batadv_skb_head_push(struct sk_buff *skb, unsigned int len); +void batadv_interface_rx(struct net_device *soft_iface, + struct sk_buff *skb, struct batadv_hard_iface *recv_if, + int hdr_size, struct batadv_orig_node *orig_node); +struct net_device *batadv_softif_create(const char *name); +void batadv_softif_destroy_sysfs(struct net_device *soft_iface); +int batadv_softif_is_valid(const struct net_device *net_dev); +extern struct rtnl_link_ops batadv_link_ops; + +#endif /* _NET_BATMAN_ADV_SOFT_INTERFACE_H_ */ diff --git a/net/batman-adv/sysfs.c b/net/batman-adv/sysfs.c new file mode 100644 index 00000000..929e304d --- /dev/null +++ b/net/batman-adv/sysfs.c @@ -0,0 +1,742 @@ +/* Copyright (C) 2010-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 "sysfs.h" +#include "translation-table.h" +#include "distributed-arp-table.h" +#include "originator.h" +#include "hard-interface.h" +#include "gateway_common.h" +#include "gateway_client.h" +#include "vis.h" + +static struct net_device *batadv_kobj_to_netdev(struct kobject *obj) +{ + struct device *dev = container_of(obj->parent, struct device, kobj); + return to_net_dev(dev); +} + +static struct batadv_priv *batadv_kobj_to_batpriv(struct kobject *obj) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(obj); + return netdev_priv(net_dev); +} + +#define BATADV_UEV_TYPE_VAR "BATTYPE=" +#define BATADV_UEV_ACTION_VAR "BATACTION=" +#define BATADV_UEV_DATA_VAR "BATDATA=" + +static char *batadv_uev_action_str[] = { + "add", + "del", + "change" +}; + +static char *batadv_uev_type_str[] = { + "gw" +}; + +/* Use this, if you have customized show and store functions */ +#define BATADV_ATTR(_name, _mode, _show, _store) \ +struct batadv_attribute batadv_attr_##_name = { \ + .attr = {.name = __stringify(_name), \ + .mode = _mode }, \ + .show = _show, \ + .store = _store, \ +}; + +#define BATADV_ATTR_SIF_STORE_BOOL(_name, _post_func) \ +ssize_t batadv_store_##_name(struct kobject *kobj, \ + struct attribute *attr, char *buff, \ + size_t count) \ +{ \ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); \ + struct batadv_priv *bat_priv = netdev_priv(net_dev); \ + return __batadv_store_bool_attr(buff, count, _post_func, attr, \ + &bat_priv->_name, net_dev); \ +} + +#define BATADV_ATTR_SIF_SHOW_BOOL(_name) \ +ssize_t batadv_show_##_name(struct kobject *kobj, \ + struct attribute *attr, char *buff) \ +{ \ + struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj); \ + return sprintf(buff, "%s\n", \ + atomic_read(&bat_priv->_name) == 0 ? \ + "disabled" : "enabled"); \ +} \ + +/* Use this, if you are going to turn a [name] in the soft-interface + * (bat_priv) on or off + */ +#define BATADV_ATTR_SIF_BOOL(_name, _mode, _post_func) \ + static BATADV_ATTR_SIF_STORE_BOOL(_name, _post_func) \ + static BATADV_ATTR_SIF_SHOW_BOOL(_name) \ + static BATADV_ATTR(_name, _mode, batadv_show_##_name, \ + batadv_store_##_name) + + +#define BATADV_ATTR_SIF_STORE_UINT(_name, _min, _max, _post_func) \ +ssize_t batadv_store_##_name(struct kobject *kobj, \ + struct attribute *attr, char *buff, \ + size_t count) \ +{ \ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); \ + struct batadv_priv *bat_priv = netdev_priv(net_dev); \ + return __batadv_store_uint_attr(buff, count, _min, _max, \ + _post_func, attr, \ + &bat_priv->_name, net_dev); \ +} + +#define BATADV_ATTR_SIF_SHOW_UINT(_name) \ +ssize_t batadv_show_##_name(struct kobject *kobj, \ + struct attribute *attr, char *buff) \ +{ \ + struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj); \ + return sprintf(buff, "%i\n", atomic_read(&bat_priv->_name)); \ +} \ + +/* Use this, if you are going to set [name] in the soft-interface + * (bat_priv) to an unsigned integer value + */ +#define BATADV_ATTR_SIF_UINT(_name, _mode, _min, _max, _post_func) \ + static BATADV_ATTR_SIF_STORE_UINT(_name, _min, _max, _post_func)\ + static BATADV_ATTR_SIF_SHOW_UINT(_name) \ + static BATADV_ATTR(_name, _mode, batadv_show_##_name, \ + batadv_store_##_name) + + +static int batadv_store_bool_attr(char *buff, size_t count, + struct net_device *net_dev, + const char *attr_name, atomic_t *attr) +{ + int enabled = -1; + + if (buff[count - 1] == '\n') + buff[count - 1] = '\0'; + + if ((strncmp(buff, "1", 2) == 0) || + (strncmp(buff, "enable", 7) == 0) || + (strncmp(buff, "enabled", 8) == 0)) + enabled = 1; + + if ((strncmp(buff, "0", 2) == 0) || + (strncmp(buff, "disable", 8) == 0) || + (strncmp(buff, "disabled", 9) == 0)) + enabled = 0; + + if (enabled < 0) { + batadv_info(net_dev, "%s: Invalid parameter received: %s\n", + attr_name, buff); + return -EINVAL; + } + + if (atomic_read(attr) == enabled) + return count; + + batadv_info(net_dev, "%s: Changing from: %s to: %s\n", attr_name, + atomic_read(attr) == 1 ? "enabled" : "disabled", + enabled == 1 ? "enabled" : "disabled"); + + atomic_set(attr, (unsigned int)enabled); + return count; +} + +static inline ssize_t +__batadv_store_bool_attr(char *buff, size_t count, + void (*post_func)(struct net_device *), + struct attribute *attr, + atomic_t *attr_store, struct net_device *net_dev) +{ + int ret; + + ret = batadv_store_bool_attr(buff, count, net_dev, attr->name, + attr_store); + if (post_func && ret) + post_func(net_dev); + + return ret; +} + +static int batadv_store_uint_attr(const char *buff, size_t count, + struct net_device *net_dev, + const char *attr_name, + unsigned int min, unsigned int max, + atomic_t *attr) +{ + unsigned long uint_val; + int ret; + + ret = kstrtoul(buff, 10, &uint_val); + if (ret) { + batadv_info(net_dev, "%s: Invalid parameter received: %s\n", + attr_name, buff); + return -EINVAL; + } + + if (uint_val < min) { + batadv_info(net_dev, "%s: Value is too small: %lu min: %u\n", + attr_name, uint_val, min); + return -EINVAL; + } + + if (uint_val > max) { + batadv_info(net_dev, "%s: Value is too big: %lu max: %u\n", + attr_name, uint_val, max); + return -EINVAL; + } + + if (atomic_read(attr) == uint_val) + return count; + + batadv_info(net_dev, "%s: Changing from: %i to: %lu\n", + attr_name, atomic_read(attr), uint_val); + + atomic_set(attr, uint_val); + return count; +} + +static inline ssize_t +__batadv_store_uint_attr(const char *buff, size_t count, + int min, int max, + void (*post_func)(struct net_device *), + const struct attribute *attr, + atomic_t *attr_store, struct net_device *net_dev) +{ + int ret; + + ret = batadv_store_uint_attr(buff, count, net_dev, attr->name, min, max, + attr_store); + if (post_func && ret) + post_func(net_dev); + + return ret; +} + +static ssize_t batadv_show_vis_mode(struct kobject *kobj, + struct attribute *attr, char *buff) +{ + struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj); + int vis_mode = atomic_read(&bat_priv->vis_mode); + const char *mode; + + if (vis_mode == BATADV_VIS_TYPE_CLIENT_UPDATE) + mode = "client"; + else + mode = "server"; + + return sprintf(buff, "%s\n", mode); +} + +static ssize_t batadv_store_vis_mode(struct kobject *kobj, + struct attribute *attr, char *buff, + size_t count) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); + struct batadv_priv *bat_priv = netdev_priv(net_dev); + unsigned long val; + int ret, vis_mode_tmp = -1; + const char *old_mode, *new_mode; + + ret = kstrtoul(buff, 10, &val); + + if (((count == 2) && (!ret) && + (val == BATADV_VIS_TYPE_CLIENT_UPDATE)) || + (strncmp(buff, "client", 6) == 0) || + (strncmp(buff, "off", 3) == 0)) + vis_mode_tmp = BATADV_VIS_TYPE_CLIENT_UPDATE; + + if (((count == 2) && (!ret) && + (val == BATADV_VIS_TYPE_SERVER_SYNC)) || + (strncmp(buff, "server", 6) == 0)) + vis_mode_tmp = BATADV_VIS_TYPE_SERVER_SYNC; + + if (vis_mode_tmp < 0) { + if (buff[count - 1] == '\n') + buff[count - 1] = '\0'; + + batadv_info(net_dev, + "Invalid parameter for 'vis mode' setting received: %s\n", + buff); + return -EINVAL; + } + + if (atomic_read(&bat_priv->vis_mode) == vis_mode_tmp) + return count; + + if (atomic_read(&bat_priv->vis_mode) == BATADV_VIS_TYPE_CLIENT_UPDATE) + old_mode = "client"; + else + old_mode = "server"; + + if (vis_mode_tmp == BATADV_VIS_TYPE_CLIENT_UPDATE) + new_mode = "client"; + else + new_mode = "server"; + + batadv_info(net_dev, "Changing vis mode from: %s to: %s\n", old_mode, + new_mode); + + atomic_set(&bat_priv->vis_mode, (unsigned int)vis_mode_tmp); + return count; +} + +static ssize_t batadv_show_bat_algo(struct kobject *kobj, + struct attribute *attr, char *buff) +{ + struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj); + return sprintf(buff, "%s\n", bat_priv->bat_algo_ops->name); +} + +static void batadv_post_gw_deselect(struct net_device *net_dev) +{ + struct batadv_priv *bat_priv = netdev_priv(net_dev); + batadv_gw_deselect(bat_priv); +} + +static ssize_t batadv_show_gw_mode(struct kobject *kobj, struct attribute *attr, + char *buff) +{ + struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj); + int bytes_written; + + switch (atomic_read(&bat_priv->gw_mode)) { + case BATADV_GW_MODE_CLIENT: + bytes_written = sprintf(buff, "%s\n", + BATADV_GW_MODE_CLIENT_NAME); + break; + case BATADV_GW_MODE_SERVER: + bytes_written = sprintf(buff, "%s\n", + BATADV_GW_MODE_SERVER_NAME); + break; + default: + bytes_written = sprintf(buff, "%s\n", + BATADV_GW_MODE_OFF_NAME); + break; + } + + return bytes_written; +} + +static ssize_t batadv_store_gw_mode(struct kobject *kobj, + struct attribute *attr, char *buff, + size_t count) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); + struct batadv_priv *bat_priv = netdev_priv(net_dev); + char *curr_gw_mode_str; + int gw_mode_tmp = -1; + + if (buff[count - 1] == '\n') + buff[count - 1] = '\0'; + + if (strncmp(buff, BATADV_GW_MODE_OFF_NAME, + strlen(BATADV_GW_MODE_OFF_NAME)) == 0) + gw_mode_tmp = BATADV_GW_MODE_OFF; + + if (strncmp(buff, BATADV_GW_MODE_CLIENT_NAME, + strlen(BATADV_GW_MODE_CLIENT_NAME)) == 0) + gw_mode_tmp = BATADV_GW_MODE_CLIENT; + + if (strncmp(buff, BATADV_GW_MODE_SERVER_NAME, + strlen(BATADV_GW_MODE_SERVER_NAME)) == 0) + gw_mode_tmp = BATADV_GW_MODE_SERVER; + + if (gw_mode_tmp < 0) { + batadv_info(net_dev, + "Invalid parameter for 'gw mode' setting received: %s\n", + buff); + return -EINVAL; + } + + if (atomic_read(&bat_priv->gw_mode) == gw_mode_tmp) + return count; + + switch (atomic_read(&bat_priv->gw_mode)) { + case BATADV_GW_MODE_CLIENT: + curr_gw_mode_str = BATADV_GW_MODE_CLIENT_NAME; + break; + case BATADV_GW_MODE_SERVER: + curr_gw_mode_str = BATADV_GW_MODE_SERVER_NAME; + break; + default: + curr_gw_mode_str = BATADV_GW_MODE_OFF_NAME; + break; + } + + batadv_info(net_dev, "Changing gw mode from: %s to: %s\n", + curr_gw_mode_str, buff); + + batadv_gw_deselect(bat_priv); + atomic_set(&bat_priv->gw_mode, (unsigned int)gw_mode_tmp); + return count; +} + +static ssize_t batadv_show_gw_bwidth(struct kobject *kobj, + struct attribute *attr, char *buff) +{ + struct batadv_priv *bat_priv = batadv_kobj_to_batpriv(kobj); + int down, up; + int gw_bandwidth = atomic_read(&bat_priv->gw_bandwidth); + + batadv_gw_bandwidth_to_kbit(gw_bandwidth, &down, &up); + return sprintf(buff, "%i%s/%i%s\n", + (down > 2048 ? down / 1024 : down), + (down > 2048 ? "MBit" : "KBit"), + (up > 2048 ? up / 1024 : up), + (up > 2048 ? "MBit" : "KBit")); +} + +static ssize_t batadv_store_gw_bwidth(struct kobject *kobj, + struct attribute *attr, char *buff, + size_t count) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); + + if (buff[count - 1] == '\n') + buff[count - 1] = '\0'; + + return batadv_gw_bandwidth_set(net_dev, buff, count); +} + +BATADV_ATTR_SIF_BOOL(aggregated_ogms, S_IRUGO | S_IWUSR, NULL); +BATADV_ATTR_SIF_BOOL(bonding, S_IRUGO | S_IWUSR, NULL); +#ifdef CONFIG_BATMAN_ADV_BLA +BATADV_ATTR_SIF_BOOL(bridge_loop_avoidance, S_IRUGO | S_IWUSR, NULL); +#endif +#ifdef CONFIG_BATMAN_ADV_DAT +BATADV_ATTR_SIF_BOOL(distributed_arp_table, S_IRUGO | S_IWUSR, NULL); +#endif +BATADV_ATTR_SIF_BOOL(fragmentation, S_IRUGO | S_IWUSR, batadv_update_min_mtu); +BATADV_ATTR_SIF_BOOL(ap_isolation, S_IRUGO | S_IWUSR, NULL); +static BATADV_ATTR(vis_mode, S_IRUGO | S_IWUSR, batadv_show_vis_mode, + batadv_store_vis_mode); +static BATADV_ATTR(routing_algo, S_IRUGO, batadv_show_bat_algo, NULL); +static BATADV_ATTR(gw_mode, S_IRUGO | S_IWUSR, batadv_show_gw_mode, + batadv_store_gw_mode); +BATADV_ATTR_SIF_UINT(orig_interval, S_IRUGO | S_IWUSR, 2 * BATADV_JITTER, + INT_MAX, NULL); +BATADV_ATTR_SIF_UINT(hop_penalty, S_IRUGO | S_IWUSR, 0, BATADV_TQ_MAX_VALUE, + NULL); +BATADV_ATTR_SIF_UINT(gw_sel_class, S_IRUGO | S_IWUSR, 1, BATADV_TQ_MAX_VALUE, + batadv_post_gw_deselect); +static BATADV_ATTR(gw_bandwidth, S_IRUGO | S_IWUSR, batadv_show_gw_bwidth, + batadv_store_gw_bwidth); +#ifdef CONFIG_BATMAN_ADV_DEBUG +BATADV_ATTR_SIF_UINT(log_level, S_IRUGO | S_IWUSR, 0, BATADV_DBG_ALL, NULL); +#endif +#ifdef CONFIG_BATMAN_ADV_NC +BATADV_ATTR_SIF_BOOL(network_coding, S_IRUGO | S_IWUSR, NULL); +#endif + +static struct batadv_attribute *batadv_mesh_attrs[] = { + &batadv_attr_aggregated_ogms, + &batadv_attr_bonding, +#ifdef CONFIG_BATMAN_ADV_BLA + &batadv_attr_bridge_loop_avoidance, +#endif +#ifdef CONFIG_BATMAN_ADV_DAT + &batadv_attr_distributed_arp_table, +#endif + &batadv_attr_fragmentation, + &batadv_attr_ap_isolation, + &batadv_attr_vis_mode, + &batadv_attr_routing_algo, + &batadv_attr_gw_mode, + &batadv_attr_orig_interval, + &batadv_attr_hop_penalty, + &batadv_attr_gw_sel_class, + &batadv_attr_gw_bandwidth, +#ifdef CONFIG_BATMAN_ADV_DEBUG + &batadv_attr_log_level, +#endif +#ifdef CONFIG_BATMAN_ADV_NC + &batadv_attr_network_coding, +#endif + NULL, +}; + +int batadv_sysfs_add_meshif(struct net_device *dev) +{ + struct kobject *batif_kobject = &dev->dev.kobj; + struct batadv_priv *bat_priv = netdev_priv(dev); + struct batadv_attribute **bat_attr; + int err; + + bat_priv->mesh_obj = kobject_create_and_add(BATADV_SYSFS_IF_MESH_SUBDIR, + batif_kobject); + if (!bat_priv->mesh_obj) { + batadv_err(dev, "Can't add sysfs directory: %s/%s\n", dev->name, + BATADV_SYSFS_IF_MESH_SUBDIR); + goto out; + } + + for (bat_attr = batadv_mesh_attrs; *bat_attr; ++bat_attr) { + err = sysfs_create_file(bat_priv->mesh_obj, + &((*bat_attr)->attr)); + if (err) { + batadv_err(dev, "Can't add sysfs file: %s/%s/%s\n", + dev->name, BATADV_SYSFS_IF_MESH_SUBDIR, + ((*bat_attr)->attr).name); + goto rem_attr; + } + } + + return 0; + +rem_attr: + for (bat_attr = batadv_mesh_attrs; *bat_attr; ++bat_attr) + sysfs_remove_file(bat_priv->mesh_obj, &((*bat_attr)->attr)); + + kobject_put(bat_priv->mesh_obj); + bat_priv->mesh_obj = NULL; +out: + return -ENOMEM; +} + +void batadv_sysfs_del_meshif(struct net_device *dev) +{ + struct batadv_priv *bat_priv = netdev_priv(dev); + struct batadv_attribute **bat_attr; + + for (bat_attr = batadv_mesh_attrs; *bat_attr; ++bat_attr) + sysfs_remove_file(bat_priv->mesh_obj, &((*bat_attr)->attr)); + + kobject_put(bat_priv->mesh_obj); + bat_priv->mesh_obj = NULL; +} + +static ssize_t batadv_show_mesh_iface(struct kobject *kobj, + struct attribute *attr, char *buff) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); + struct batadv_hard_iface *hard_iface; + ssize_t length; + const char *ifname; + + hard_iface = batadv_hardif_get_by_netdev(net_dev); + if (!hard_iface) + return 0; + + if (hard_iface->if_status == BATADV_IF_NOT_IN_USE) + ifname = "none"; + else + ifname = hard_iface->soft_iface->name; + + length = sprintf(buff, "%s\n", ifname); + + batadv_hardif_free_ref(hard_iface); + + return length; +} + +static ssize_t batadv_store_mesh_iface(struct kobject *kobj, + struct attribute *attr, char *buff, + size_t count) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); + struct batadv_hard_iface *hard_iface; + int status_tmp = -1; + int ret = count; + + hard_iface = batadv_hardif_get_by_netdev(net_dev); + if (!hard_iface) + return count; + + if (buff[count - 1] == '\n') + buff[count - 1] = '\0'; + + if (strlen(buff) >= IFNAMSIZ) { + pr_err("Invalid parameter for 'mesh_iface' setting received: interface name too long '%s'\n", + buff); + batadv_hardif_free_ref(hard_iface); + return -EINVAL; + } + + if (strncmp(buff, "none", 4) == 0) + status_tmp = BATADV_IF_NOT_IN_USE; + else + status_tmp = BATADV_IF_I_WANT_YOU; + + if (hard_iface->if_status == status_tmp) + goto out; + + if ((hard_iface->soft_iface) && + (strncmp(hard_iface->soft_iface->name, buff, IFNAMSIZ) == 0)) + goto out; + + rtnl_lock(); + + if (status_tmp == BATADV_IF_NOT_IN_USE) { + batadv_hardif_disable_interface(hard_iface, + BATADV_IF_CLEANUP_AUTO); + goto unlock; + } + + /* if the interface already is in use */ + if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) + batadv_hardif_disable_interface(hard_iface, + BATADV_IF_CLEANUP_AUTO); + + ret = batadv_hardif_enable_interface(hard_iface, buff); + +unlock: + rtnl_unlock(); +out: + batadv_hardif_free_ref(hard_iface); + return ret; +} + +static ssize_t batadv_show_iface_status(struct kobject *kobj, + struct attribute *attr, char *buff) +{ + struct net_device *net_dev = batadv_kobj_to_netdev(kobj); + struct batadv_hard_iface *hard_iface; + ssize_t length; + + hard_iface = batadv_hardif_get_by_netdev(net_dev); + if (!hard_iface) + return 0; + + switch (hard_iface->if_status) { + case BATADV_IF_TO_BE_REMOVED: + length = sprintf(buff, "disabling\n"); + break; + case BATADV_IF_INACTIVE: + length = sprintf(buff, "inactive\n"); + break; + case BATADV_IF_ACTIVE: + length = sprintf(buff, "active\n"); + break; + case BATADV_IF_TO_BE_ACTIVATED: + length = sprintf(buff, "enabling\n"); + break; + case BATADV_IF_NOT_IN_USE: + default: + length = sprintf(buff, "not in use\n"); + break; + } + + batadv_hardif_free_ref(hard_iface); + + return length; +} + +static BATADV_ATTR(mesh_iface, S_IRUGO | S_IWUSR, batadv_show_mesh_iface, + batadv_store_mesh_iface); +static BATADV_ATTR(iface_status, S_IRUGO, batadv_show_iface_status, NULL); + +static struct batadv_attribute *batadv_batman_attrs[] = { + &batadv_attr_mesh_iface, + &batadv_attr_iface_status, + NULL, +}; + +int batadv_sysfs_add_hardif(struct kobject **hardif_obj, struct net_device *dev) +{ + struct kobject *hardif_kobject = &dev->dev.kobj; + struct batadv_attribute **bat_attr; + int err; + + *hardif_obj = kobject_create_and_add(BATADV_SYSFS_IF_BAT_SUBDIR, + hardif_kobject); + + if (!*hardif_obj) { + batadv_err(dev, "Can't add sysfs directory: %s/%s\n", dev->name, + BATADV_SYSFS_IF_BAT_SUBDIR); + goto out; + } + + for (bat_attr = batadv_batman_attrs; *bat_attr; ++bat_attr) { + err = sysfs_create_file(*hardif_obj, &((*bat_attr)->attr)); + if (err) { + batadv_err(dev, "Can't add sysfs file: %s/%s/%s\n", + dev->name, BATADV_SYSFS_IF_BAT_SUBDIR, + ((*bat_attr)->attr).name); + goto rem_attr; + } + } + + return 0; + +rem_attr: + for (bat_attr = batadv_batman_attrs; *bat_attr; ++bat_attr) + sysfs_remove_file(*hardif_obj, &((*bat_attr)->attr)); +out: + return -ENOMEM; +} + +void batadv_sysfs_del_hardif(struct kobject **hardif_obj) +{ + kobject_put(*hardif_obj); + *hardif_obj = NULL; +} + +int batadv_throw_uevent(struct batadv_priv *bat_priv, enum batadv_uev_type type, + enum batadv_uev_action action, const char *data) +{ + int ret = -ENOMEM; + struct kobject *bat_kobj; + char *uevent_env[4] = { NULL, NULL, NULL, NULL }; + + bat_kobj = &bat_priv->soft_iface->dev.kobj; + + uevent_env[0] = kmalloc(strlen(BATADV_UEV_TYPE_VAR) + + strlen(batadv_uev_type_str[type]) + 1, + GFP_ATOMIC); + if (!uevent_env[0]) + goto out; + + sprintf(uevent_env[0], "%s%s", BATADV_UEV_TYPE_VAR, + batadv_uev_type_str[type]); + + uevent_env[1] = kmalloc(strlen(BATADV_UEV_ACTION_VAR) + + strlen(batadv_uev_action_str[action]) + 1, + GFP_ATOMIC); + if (!uevent_env[1]) + goto out; + + sprintf(uevent_env[1], "%s%s", BATADV_UEV_ACTION_VAR, + batadv_uev_action_str[action]); + + /* If the event is DEL, ignore the data field */ + if (action != BATADV_UEV_DEL) { + uevent_env[2] = kmalloc(strlen(BATADV_UEV_DATA_VAR) + + strlen(data) + 1, GFP_ATOMIC); + if (!uevent_env[2]) + goto out; + + sprintf(uevent_env[2], "%s%s", BATADV_UEV_DATA_VAR, data); + } + + ret = kobject_uevent_env(bat_kobj, KOBJ_CHANGE, uevent_env); +out: + kfree(uevent_env[0]); + kfree(uevent_env[1]); + kfree(uevent_env[2]); + + if (ret) + batadv_dbg(BATADV_DBG_BATMAN, bat_priv, + "Impossible to send uevent for (%s,%s,%s) event (err: %d)\n", + batadv_uev_type_str[type], + batadv_uev_action_str[action], + (action == BATADV_UEV_DEL ? "NULL" : data), ret); + return ret; +} diff --git a/net/batman-adv/sysfs.h b/net/batman-adv/sysfs.h new file mode 100644 index 00000000..479acf4c --- /dev/null +++ b/net/batman-adv/sysfs.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2010-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_SYSFS_H_ +#define _NET_BATMAN_ADV_SYSFS_H_ + +#define BATADV_SYSFS_IF_MESH_SUBDIR "mesh" +#define BATADV_SYSFS_IF_BAT_SUBDIR "batman_adv" + +struct batadv_attribute { + struct attribute attr; + ssize_t (*show)(struct kobject *kobj, struct attribute *attr, + char *buf); + ssize_t (*store)(struct kobject *kobj, struct attribute *attr, + char *buf, size_t count); +}; + +int batadv_sysfs_add_meshif(struct net_device *dev); +void batadv_sysfs_del_meshif(struct net_device *dev); +int batadv_sysfs_add_hardif(struct kobject **hardif_obj, + struct net_device *dev); +void batadv_sysfs_del_hardif(struct kobject **hardif_obj); +int batadv_throw_uevent(struct batadv_priv *bat_priv, enum batadv_uev_type type, + enum batadv_uev_action action, const char *data); + +#endif /* _NET_BATMAN_ADV_SYSFS_H_ */ diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c new file mode 100644 index 00000000..9e874857 --- /dev/null +++ b/net/batman-adv/translation-table.c @@ -0,0 +1,2578 @@ +/* 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/crc16.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, + 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, + 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); +} + +static struct batadv_tt_common_entry * +batadv_tt_hash_find(struct batadv_hashtable *hash, const void *data) +{ + struct hlist_head *head; + struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_common_entry *tt_common_entry_tmp = NULL; + uint32_t index; + + if (!hash) + return NULL; + + index = batadv_choose_orig(data, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(tt_common_entry, head, hash_entry) { + if (!batadv_compare_eth(tt_common_entry, data)) + continue; + + if (!atomic_inc_not_zero(&tt_common_entry->refcount)) + continue; + + tt_common_entry_tmp = tt_common_entry; + break; + } + rcu_read_unlock(); + + return tt_common_entry_tmp; +} + +static struct batadv_tt_local_entry * +batadv_tt_local_hash_find(struct batadv_priv *bat_priv, const void *data) +{ + 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, data); + if (tt_common_entry) + tt_local_entry = container_of(tt_common_entry, + struct batadv_tt_local_entry, + common); + return tt_local_entry; +} + +static struct batadv_tt_global_entry * +batadv_tt_global_hash_find(struct batadv_priv *bat_priv, const void *data) +{ + 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, data); + 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); +} + +static void batadv_tt_global_entry_free_rcu(struct rcu_head *rcu) +{ + struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_global_entry *tt_global_entry; + + tt_common_entry = container_of(rcu, struct batadv_tt_common_entry, rcu); + tt_global_entry = container_of(tt_common_entry, + struct batadv_tt_global_entry, common); + + kfree(tt_global_entry); +} + +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); + call_rcu(&tt_global_entry->common.rcu, + batadv_tt_global_entry_free_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); +} + +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; + /* to avoid race conditions, immediately decrease the tt counter */ + atomic_dec(&orig_entry->orig_node->tt_size); + call_rcu(&orig_entry->rcu, batadv_tt_orig_list_entry_free_rcu); +} + +static void batadv_tt_local_event(struct batadv_priv *bat_priv, + const uint8_t *addr, uint8_t flags) +{ + struct batadv_tt_change_node *tt_change_node, *entry, *safe; + 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; + memcpy(tt_change_node->change.addr, addr, ETH_ALEN); + + 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, 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); +} + +int batadv_tt_len(int changes_num) +{ + return changes_num * sizeof(struct batadv_tt_change); +} + +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: %s\n", + tt_global->common.addr, message); + + batadv_hash_remove(bat_priv->tt.global_hash, batadv_compare_tt, + batadv_choose_orig, tt_global->common.addr); + batadv_tt_global_entry_free_ref(tt_global); +} + +void batadv_tt_local_add(struct net_device *soft_iface, const uint8_t *addr, + 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); + tt_global = batadv_tt_global_hash_find(bat_priv, addr); + + 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\n", addr); + /* 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 came back to its original location\n", + addr); + /* 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 (ttvn: %d)\n", addr, + (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; + 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_orig, &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, addr, tt_local->common.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, + 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); +} + +static void batadv_tt_realloc_packet_buff(unsigned char **packet_buff, + int *packet_buff_len, + int min_packet_len, + int new_packet_len) +{ + unsigned char *new_buff; + + new_buff = kmalloc(new_packet_len, GFP_ATOMIC); + + /* keep old buffer if kmalloc should fail */ + if (new_buff) { + memcpy(new_buff, *packet_buff, min_packet_len); + kfree(*packet_buff); + *packet_buff = new_buff; + *packet_buff_len = new_packet_len; + } +} + +static void batadv_tt_prepare_packet_buff(struct batadv_priv *bat_priv, + unsigned char **packet_buff, + int *packet_buff_len, + int min_packet_len) +{ + int req_len; + + req_len = min_packet_len; + req_len += batadv_tt_len(atomic_read(&bat_priv->tt.local_changes)); + + /* 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 (req_len > bat_priv->soft_iface->mtu) + req_len = min_packet_len; + + batadv_tt_realloc_packet_buff(packet_buff, packet_buff_len, + min_packet_len, req_len); +} + +static int batadv_tt_changes_fill_buff(struct batadv_priv *bat_priv, + unsigned char **packet_buff, + int *packet_buff_len, + int min_packet_len) +{ + struct batadv_tt_change_node *entry, *safe; + int count = 0, tot_changes = 0, new_len; + unsigned char *tt_buff; + + batadv_tt_prepare_packet_buff(bat_priv, packet_buff, + packet_buff_len, min_packet_len); + + new_len = *packet_buff_len - min_packet_len; + tt_buff = *packet_buff + min_packet_len; + + if (new_len > 0) + tot_changes = new_len / batadv_tt_len(1); + + 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 (count < tot_changes) { + memcpy(tt_buff + batadv_tt_len(count), + &entry->change, sizeof(struct batadv_tt_change)); + 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; + /* check whether this new OGM has no changes due to size problems */ + if (new_len > 0) { + /* if kmalloc() fails we will reply with the full table + * instead of providing the diff + */ + bat_priv->tt.last_changeset = kmalloc(new_len, GFP_ATOMIC); + if (bat_priv->tt.last_changeset) { + memcpy(bat_priv->tt.last_changeset, tt_buff, new_len); + bat_priv->tt.last_changeset_len = new_len; + } + } + spin_unlock_bh(&bat_priv->tt.last_changeset_lock); + + return count; +} + +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 hlist_head *head; + 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 CRC: %#.4x):\n", + net_dev->name, (uint8_t)atomic_read(&bat_priv->tt.vn), + bat_priv->tt.local_crc); + seq_printf(seq, " %-13s %-7s %-10s\n", "Client", "Flags", + "Last seen"); + + 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); + 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; + + seq_printf(seq, " * %pM [%c%c%c%c%c] %3u.%03u\n", + tt_common_entry->addr, + (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); + } + 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->common.addr, + tt_local_entry->common.flags | 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) pending to be removed: %s\n", + tt_local_entry->common.addr, 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 + * @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, 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); + 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->common.addr, + curr_flags | 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); + atomic_inc(&orig_node->tt_size); + 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); +} + +/* caller must hold orig_node refcount */ +int batadv_tt_global_add(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *tt_addr, uint8_t flags, + uint8_t ttvn) +{ + struct batadv_tt_global_entry *tt_global_entry; + struct batadv_tt_local_entry *tt_local_entry; + int ret = 0; + int hash_added; + struct batadv_tt_common_entry *common; + uint16_t local_flags; + + tt_global_entry = batadv_tt_global_hash_find(bat_priv, tt_addr); + tt_local_entry = batadv_tt_local_hash_find(bat_priv, tt_addr); + + /* 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->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_orig, 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 (via %pM)\n", + common->addr, orig_node->orig); + ret = 1; + +out_remove: + + /* remove address from local hash if present */ + local_flags = batadv_tt_local_remove(bat_priv, tt_addr, + "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 hlist_head *head; + struct batadv_tt_orig_list_entry *orig_entry, *best_entry; + struct batadv_tt_common_entry *tt_common_entry; + uint16_t flags; + uint8_t last_ttvn; + + tt_common_entry = &tt_global_entry->common; + flags = tt_common_entry->flags; + + best_entry = batadv_transtable_best_orig(tt_global_entry); + if (best_entry) { + last_ttvn = atomic_read(&best_entry->orig_node->last_ttvn); + seq_printf(seq, + " %c %pM (%3u) via %pM (%3u) (%#.4x) [%c%c%c]\n", + '*', tt_global_entry->common.addr, + best_entry->ttvn, best_entry->orig_node->orig, + last_ttvn, best_entry->orig_node->tt_crc, + (flags & BATADV_TT_CLIENT_ROAM ? 'R' : '.'), + (flags & BATADV_TT_CLIENT_WIFI ? 'W' : '.'), + (flags & BATADV_TT_CLIENT_TEMP ? 'T' : '.')); + } + + head = &tt_global_entry->orig_list; + + hlist_for_each_entry_rcu(orig_entry, head, list) { + if (best_entry == orig_entry) + continue; + + last_ttvn = atomic_read(&orig_entry->orig_node->last_ttvn); + seq_printf(seq, " %c %pM (%3u) via %pM (%3u) [%c%c%c]\n", + '+', tt_global_entry->common.addr, + orig_entry->ttvn, orig_entry->orig_node->orig, + last_ttvn, + (flags & BATADV_TT_CLIENT_ROAM ? 'R' : '.'), + (flags & BATADV_TT_CLIENT_WIFI ? 'W' : '.'), + (flags & BATADV_TT_CLIENT_TEMP ? 'T' : '.')); + } +} + +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 %-15s %s (%-6s) %s\n", + "Client", "(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_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; + + 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) { + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Deleting %pM from global tt entry %pM: %s\n", + orig_node->orig, + tt_global_entry->common.addr, message); + hlist_del_rcu(&orig_entry->list); + 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); +} + + + +static void batadv_tt_global_del(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *addr, + 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); + 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); + 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); +} + +void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + 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 */ + + 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) { + 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)) { + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Deleting global tt entry %pM: %s\n", + tt_global->common.addr, 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): %s\n", + tt_global->common.addr, 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; +} + +struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv, + const uint8_t *src, + const uint8_t *addr) +{ + 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; + + if (src && atomic_read(&bat_priv->ap_isolation)) { + tt_local_entry = batadv_tt_local_hash_find(bat_priv, src); + 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); + 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; +} + +/* Calculates the checksum of the local table of a given orig_node */ +static uint16_t batadv_tt_global_crc(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node) +{ + uint16_t total = 0, total_one; + 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; + int j; + + 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); + /* 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; + + total_one = 0; + for (j = 0; j < ETH_ALEN; j++) + total_one = crc16_byte(total_one, + tt_common->addr[j]); + total ^= total_one; + } + rcu_read_unlock(); + } + + return total; +} + +/* Calculates the checksum of the local table */ +static uint16_t batadv_tt_local_crc(struct batadv_priv *bat_priv) +{ + uint16_t total = 0, total_one; + struct batadv_hashtable *hash = bat_priv->tt.local_hash; + struct batadv_tt_common_entry *tt_common; + struct hlist_head *head; + uint32_t i; + int j; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(tt_common, head, hash_entry) { + /* not yet committed clients have not to be taken into + * account while computing the CRC + */ + if (tt_common->flags & BATADV_TT_CLIENT_NEW) + continue; + total_one = 0; + for (j = 0; j < ETH_ALEN; j++) + total_one = crc16_byte(total_one, + tt_common->addr[j]); + total ^= total_one; + } + rcu_read_unlock(); + } + + return total; +} + +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 unsigned char *tt_buff, + uint8_t tt_num_changes) +{ + uint16_t tt_buff_len = batadv_tt_len(tt_num_changes); + + /* 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; +} + +/* data_ptr is useless here, but has to be kept to respect the prototype */ +static int batadv_tt_local_valid_entry(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); +} + +static struct sk_buff * +batadv_tt_response_fill_table(uint16_t tt_len, uint8_t ttvn, + struct batadv_hashtable *hash, + struct batadv_priv *bat_priv, + int (*valid_cb)(const void *, const void *), + void *cb_data) +{ + struct batadv_tt_common_entry *tt_common_entry; + struct batadv_tt_query_packet *tt_response; + struct batadv_tt_change *tt_change; + struct hlist_head *head; + struct sk_buff *skb = NULL; + uint16_t tt_tot, tt_count; + ssize_t tt_query_size = sizeof(struct batadv_tt_query_packet); + uint32_t i; + size_t len; + + if (tt_query_size + tt_len > bat_priv->soft_iface->mtu) { + tt_len = bat_priv->soft_iface->mtu - tt_query_size; + tt_len -= tt_len % sizeof(struct batadv_tt_change); + } + tt_tot = tt_len / sizeof(struct batadv_tt_change); + + len = tt_query_size + tt_len; + skb = dev_alloc_skb(len + ETH_HLEN + NET_IP_ALIGN); + if (!skb) + goto out; + + skb_reserve(skb, ETH_HLEN + NET_IP_ALIGN); + tt_response = (struct batadv_tt_query_packet *)skb_put(skb, len); + tt_response->ttvn = ttvn; + + tt_change = (struct batadv_tt_change *)(skb->data + tt_query_size); + tt_count = 0; + + 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_count == tt_tot) + 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_count++; + tt_change++; + } + } + rcu_read_unlock(); + + /* store in the message the number of entries we have successfully + * copied + */ + tt_response->tt_data = htons(tt_count); + +out: + return skb; +} + +static int batadv_send_tt_request(struct batadv_priv *bat_priv, + struct batadv_orig_node *dst_orig_node, + uint8_t ttvn, uint16_t tt_crc, + bool full_table) +{ + struct sk_buff *skb = NULL; + struct batadv_tt_query_packet *tt_request; + struct batadv_hard_iface *primary_if; + struct batadv_tt_req_node *tt_req_node = NULL; + int ret = 1; + size_t tt_req_len; + + 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; + + skb = dev_alloc_skb(sizeof(*tt_request) + ETH_HLEN + NET_IP_ALIGN); + if (!skb) + goto out; + + skb_reserve(skb, ETH_HLEN + NET_IP_ALIGN); + + tt_req_len = sizeof(*tt_request); + tt_request = (struct batadv_tt_query_packet *)skb_put(skb, tt_req_len); + + tt_request->header.packet_type = BATADV_TT_QUERY; + tt_request->header.version = BATADV_COMPAT_VERSION; + memcpy(tt_request->src, primary_if->net_dev->dev_addr, ETH_ALEN); + memcpy(tt_request->dst, dst_orig_node->orig, ETH_ALEN); + tt_request->header.ttl = BATADV_TTL; + tt_request->ttvn = ttvn; + tt_request->tt_data = htons(tt_crc); + tt_request->flags = BATADV_TT_REQUEST; + + if (full_table) + tt_request->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); + + if (batadv_send_skb_to_orig(skb, dst_orig_node, NULL)) + ret = 0; + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (ret) + kfree_skb(skb); + 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); + } + return ret; +} + +static bool +batadv_send_other_tt_response(struct batadv_priv *bat_priv, + struct batadv_tt_query_packet *tt_request) +{ + struct batadv_orig_node *req_dst_orig_node; + struct batadv_orig_node *res_dst_orig_node = NULL; + uint8_t orig_ttvn, req_ttvn, ttvn; + int ret = false; + unsigned char *tt_buff; + bool full_table; + uint16_t tt_len, tt_tot; + struct sk_buff *skb = NULL; + struct batadv_tt_query_packet *tt_response; + uint8_t *packet_pos; + size_t len; + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Received TT_REQUEST from %pM for ttvn: %u (%pM) [%c]\n", + tt_request->src, tt_request->ttvn, tt_request->dst, + (tt_request->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, tt_request->dst); + if (!req_dst_orig_node) + goto out; + + res_dst_orig_node = batadv_orig_hash_find(bat_priv, tt_request->src); + if (!res_dst_orig_node) + goto out; + + orig_ttvn = (uint8_t)atomic_read(&req_dst_orig_node->last_ttvn); + req_ttvn = tt_request->ttvn; + + /* I don't have the requested data */ + if (orig_ttvn != req_ttvn || + tt_request->tt_data != htons(req_dst_orig_node->tt_crc)) + goto out; + + /* If the full table has been explicitly requested */ + if (tt_request->flags & BATADV_TT_FULL_TABLE || + !req_dst_orig_node->tt_buff) + full_table = true; + else + full_table = false; + + /* In this version, fragmentation is not implemented, then + * I'll send only one packet with as much TT entries as I can + */ + if (!full_table) { + spin_lock_bh(&req_dst_orig_node->tt_buff_lock); + tt_len = req_dst_orig_node->tt_buff_len; + tt_tot = tt_len / sizeof(struct batadv_tt_change); + + len = sizeof(*tt_response) + tt_len; + skb = dev_alloc_skb(len + ETH_HLEN + NET_IP_ALIGN); + if (!skb) + goto unlock; + + skb_reserve(skb, ETH_HLEN + NET_IP_ALIGN); + packet_pos = skb_put(skb, len); + tt_response = (struct batadv_tt_query_packet *)packet_pos; + tt_response->ttvn = req_ttvn; + tt_response->tt_data = htons(tt_tot); + + tt_buff = skb->data + sizeof(*tt_response); + /* Copy the last orig_node's OGM buffer */ + memcpy(tt_buff, req_dst_orig_node->tt_buff, + req_dst_orig_node->tt_buff_len); + + spin_unlock_bh(&req_dst_orig_node->tt_buff_lock); + } else { + tt_len = (uint16_t)atomic_read(&req_dst_orig_node->tt_size); + tt_len *= sizeof(struct batadv_tt_change); + ttvn = (uint8_t)atomic_read(&req_dst_orig_node->last_ttvn); + + skb = batadv_tt_response_fill_table(tt_len, ttvn, + bat_priv->tt.global_hash, + bat_priv, + batadv_tt_global_valid, + req_dst_orig_node); + if (!skb) + goto out; + + tt_response = (struct batadv_tt_query_packet *)skb->data; + } + + tt_response->header.packet_type = BATADV_TT_QUERY; + tt_response->header.version = BATADV_COMPAT_VERSION; + tt_response->header.ttl = BATADV_TTL; + memcpy(tt_response->src, req_dst_orig_node->orig, ETH_ALEN); + memcpy(tt_response->dst, tt_request->src, ETH_ALEN); + tt_response->flags = BATADV_TT_RESPONSE; + + if (full_table) + tt_response->flags |= BATADV_TT_FULL_TABLE; + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Sending TT_RESPONSE %pM for %pM (ttvn: %u)\n", + res_dst_orig_node->orig, req_dst_orig_node->orig, req_ttvn); + + batadv_inc_counter(bat_priv, BATADV_CNT_TT_RESPONSE_TX); + + if (batadv_send_skb_to_orig(skb, res_dst_orig_node, NULL)) + 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); + if (!ret) + kfree_skb(skb); + return ret; +} + +static bool +batadv_send_my_tt_response(struct batadv_priv *bat_priv, + struct batadv_tt_query_packet *tt_request) +{ + struct batadv_orig_node *orig_node; + struct batadv_hard_iface *primary_if = NULL; + uint8_t my_ttvn, req_ttvn, ttvn; + int ret = false; + unsigned char *tt_buff; + bool full_table; + uint16_t tt_len, tt_tot; + struct sk_buff *skb = NULL; + struct batadv_tt_query_packet *tt_response; + uint8_t *packet_pos; + size_t len; + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Received TT_REQUEST from %pM for ttvn: %u (me) [%c]\n", + tt_request->src, tt_request->ttvn, + (tt_request->flags & BATADV_TT_FULL_TABLE ? 'F' : '.')); + + + my_ttvn = (uint8_t)atomic_read(&bat_priv->tt.vn); + req_ttvn = tt_request->ttvn; + + orig_node = batadv_orig_hash_find(bat_priv, tt_request->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_request->flags & BATADV_TT_FULL_TABLE || my_ttvn != req_ttvn || + !bat_priv->tt.last_changeset) + full_table = true; + else + full_table = false; + + /* In this version, fragmentation is not implemented, then + * I'll send only one packet with as much TT entries as I can + */ + if (!full_table) { + spin_lock_bh(&bat_priv->tt.last_changeset_lock); + tt_len = bat_priv->tt.last_changeset_len; + tt_tot = tt_len / sizeof(struct batadv_tt_change); + + len = sizeof(*tt_response) + tt_len; + skb = dev_alloc_skb(len + ETH_HLEN + NET_IP_ALIGN); + if (!skb) + goto unlock; + + skb_reserve(skb, ETH_HLEN + NET_IP_ALIGN); + packet_pos = skb_put(skb, len); + tt_response = (struct batadv_tt_query_packet *)packet_pos; + tt_response->ttvn = req_ttvn; + tt_response->tt_data = htons(tt_tot); + + tt_buff = skb->data + sizeof(*tt_response); + memcpy(tt_buff, bat_priv->tt.last_changeset, + bat_priv->tt.last_changeset_len); + spin_unlock_bh(&bat_priv->tt.last_changeset_lock); + } else { + tt_len = (uint16_t)atomic_read(&bat_priv->tt.local_entry_num); + tt_len *= sizeof(struct batadv_tt_change); + ttvn = (uint8_t)atomic_read(&bat_priv->tt.vn); + + skb = batadv_tt_response_fill_table(tt_len, ttvn, + bat_priv->tt.local_hash, + bat_priv, + batadv_tt_local_valid_entry, + NULL); + if (!skb) + goto out; + + tt_response = (struct batadv_tt_query_packet *)skb->data; + } + + tt_response->header.packet_type = BATADV_TT_QUERY; + tt_response->header.version = BATADV_COMPAT_VERSION; + tt_response->header.ttl = BATADV_TTL; + memcpy(tt_response->src, primary_if->net_dev->dev_addr, ETH_ALEN); + memcpy(tt_response->dst, tt_request->src, ETH_ALEN); + tt_response->flags = BATADV_TT_RESPONSE; + + if (full_table) + tt_response->flags |= BATADV_TT_FULL_TABLE; + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Sending TT_RESPONSE to %pM [%c]\n", + orig_node->orig, + (tt_response->flags & BATADV_TT_FULL_TABLE ? 'F' : '.')); + + batadv_inc_counter(bat_priv, BATADV_CNT_TT_RESPONSE_TX); + + if (batadv_send_skb_to_orig(skb, orig_node, NULL)) + ret = true; + goto out; + +unlock: + spin_unlock_bh(&bat_priv->tt.last_changeset_lock); +out: + if (orig_node) + batadv_orig_node_free_ref(orig_node); + if (primary_if) + batadv_hardif_free_ref(primary_if); + if (!ret) + kfree_skb(skb); + /* This packet was for me, so it doesn't need to be re-routed */ + return true; +} + +bool batadv_send_tt_response(struct batadv_priv *bat_priv, + struct batadv_tt_query_packet *tt_request) +{ + if (batadv_is_my_mac(bat_priv, tt_request->dst)) { + /* don't answer backbone gws! */ + if (batadv_bla_is_backbone_gw_orig(bat_priv, tt_request->src)) + return true; + + return batadv_send_my_tt_response(bat_priv, tt_request); + } else { + return batadv_send_other_tt_response(bat_priv, tt_request); + } +} + +static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + struct batadv_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, + "tt removed by changes", + roams); + } else { + if (!batadv_tt_global_add(bat_priv, orig_node, + (tt_change + i)->addr, + (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_tt_query_packet *tt_response) +{ + struct batadv_orig_node *orig_node; + + orig_node = batadv_orig_hash_find(bat_priv, tt_response->src); + if (!orig_node) + goto out; + + /* Purge the old table first.. */ + batadv_tt_global_del_orig(bat_priv, orig_node, "Received full table"); + + _batadv_tt_update_changes(bat_priv, orig_node, + (struct batadv_tt_change *)(tt_response + 1), + ntohs(tt_response->tt_data), + tt_response->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, tt_response->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_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, + (unsigned char *)tt_change, tt_num_changes); + atomic_set(&orig_node->last_ttvn, ttvn); +} + +bool batadv_is_my_client(struct batadv_priv *bat_priv, const uint8_t *addr) +{ + struct batadv_tt_local_entry *tt_local_entry; + bool ret = false; + + tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr); + 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; +} + +void batadv_handle_tt_response(struct batadv_priv *bat_priv, + struct batadv_tt_query_packet *tt_response) +{ + struct batadv_tt_req_node *node, *safe; + struct batadv_orig_node *orig_node = NULL; + struct batadv_tt_change *tt_change; + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Received TT_RESPONSE from %pM for ttvn %d t_size: %d [%c]\n", + tt_response->src, tt_response->ttvn, + ntohs(tt_response->tt_data), + (tt_response->flags & BATADV_TT_FULL_TABLE ? 'F' : '.')); + + /* we should have never asked a backbone gw */ + if (batadv_bla_is_backbone_gw_orig(bat_priv, tt_response->src)) + goto out; + + orig_node = batadv_orig_hash_find(bat_priv, tt_response->src); + if (!orig_node) + goto out; + + if (tt_response->flags & BATADV_TT_FULL_TABLE) { + batadv_tt_fill_gtable(bat_priv, tt_response); + } else { + tt_change = (struct batadv_tt_change *)(tt_response + 1); + batadv_tt_update_changes(bat_priv, orig_node, + ntohs(tt_response->tt_data), + tt_response->ttvn, tt_change); + } + + /* 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, tt_response->src)) + continue; + list_del(&node->list); + kfree(node); + } + spin_unlock_bh(&bat_priv->tt.req_list_lock); + + /* Recalculate the CRC for this orig_node and store it */ + orig_node->tt_crc = batadv_tt_global_crc(bat_priv, orig_node); +out: + if (orig_node) + batadv_orig_node_free_ref(orig_node); +} + +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; + + 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; +} + +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; +} + +static void batadv_send_roam_adv(struct batadv_priv *bat_priv, uint8_t *client, + struct batadv_orig_node *orig_node) +{ + struct sk_buff *skb = NULL; + struct batadv_roam_adv_packet *roam_adv_packet; + int ret = 1; + struct batadv_hard_iface *primary_if; + size_t len = sizeof(*roam_adv_packet); + + /* 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; + + skb = dev_alloc_skb(sizeof(*roam_adv_packet) + ETH_HLEN + NET_IP_ALIGN); + if (!skb) + goto out; + + skb_reserve(skb, ETH_HLEN + NET_IP_ALIGN); + + roam_adv_packet = (struct batadv_roam_adv_packet *)skb_put(skb, len); + + roam_adv_packet->header.packet_type = BATADV_ROAM_ADV; + roam_adv_packet->header.version = BATADV_COMPAT_VERSION; + roam_adv_packet->header.ttl = BATADV_TTL; + roam_adv_packet->reserved = 0; + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + memcpy(roam_adv_packet->src, primary_if->net_dev->dev_addr, ETH_ALEN); + batadv_hardif_free_ref(primary_if); + memcpy(roam_adv_packet->dst, orig_node->orig, ETH_ALEN); + memcpy(roam_adv_packet->client, client, ETH_ALEN); + + batadv_dbg(BATADV_DBG_TT, bat_priv, + "Sending ROAMING_ADV to %pM (client %pM)\n", + orig_node->orig, client); + + batadv_inc_counter(bat_priv, BATADV_CNT_TT_ROAM_ADV_TX); + + if (batadv_send_skb_to_orig(skb, orig_node, NULL)) + ret = 0; + +out: + if (ret && skb) + kfree_skb(skb); + return; +} + +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) +{ + 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); +} + +/* This function will enable or disable the specified flags for all the entries + * in the given hash table and returns the number of modified entries + */ +static uint16_t batadv_tt_set_flags(struct batadv_hashtable *hash, + uint16_t flags, bool enable) +{ + uint32_t i; + uint16_t changed_num = 0; + struct hlist_head *head; + struct batadv_tt_common_entry *tt_common_entry; + + if (!hash) + goto out; + + 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++; + } + rcu_read_unlock(); + } +out: + return changed_num; +} + +/* 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): pending\n", + tt_common->addr); + + atomic_dec(&bat_priv->tt.local_entry_num); + 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); + } +} + +static int batadv_tt_commit_changes(struct batadv_priv *bat_priv, + unsigned char **packet_buff, + int *packet_buff_len, int packet_min_len) +{ + uint16_t changed_num = 0; + + if (atomic_read(&bat_priv->tt.local_changes) < 1) + return -ENOENT; + + changed_num = batadv_tt_set_flags(bat_priv->tt.local_hash, + BATADV_TT_CLIENT_NEW, false); + + /* all reset entries have to be counted as local entries */ + atomic_add(changed_num, &bat_priv->tt.local_entry_num); + batadv_tt_local_purge_pending_clients(bat_priv); + bat_priv->tt.local_crc = batadv_tt_local_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); + + return batadv_tt_changes_fill_buff(bat_priv, packet_buff, + packet_buff_len, packet_min_len); +} + +/* when calling this function (hard_iface == primary_if) has to be true */ +int batadv_tt_append_diff(struct batadv_priv *bat_priv, + unsigned char **packet_buff, int *packet_buff_len, + int packet_min_len) +{ + int tt_num_changes; + + /* if at least one change happened */ + tt_num_changes = batadv_tt_commit_changes(bat_priv, packet_buff, + packet_buff_len, + packet_min_len); + + /* if the changes have been sent often enough */ + if ((tt_num_changes < 0) && + (!batadv_atomic_dec_not_zero(&bat_priv->tt.ogm_append_cnt))) { + batadv_tt_realloc_packet_buff(packet_buff, packet_buff_len, + packet_min_len, packet_min_len); + tt_num_changes = 0; + } + + return tt_num_changes; +} + +bool batadv_is_ap_isolated(struct batadv_priv *bat_priv, uint8_t *src, + uint8_t *dst) +{ + struct batadv_tt_local_entry *tt_local_entry = NULL; + struct batadv_tt_global_entry *tt_global_entry = NULL; + bool ret = false; + + if (!atomic_read(&bat_priv->ap_isolation)) + goto out; + + tt_local_entry = batadv_tt_local_hash_find(bat_priv, dst); + if (!tt_local_entry) + goto out; + + tt_global_entry = batadv_tt_global_hash_find(bat_priv, src); + if (!tt_global_entry) + goto out; + + if (!_batadv_is_ap_isolated(tt_local_entry, tt_global_entry)) + goto out; + + ret = true; + +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; +} + +void batadv_tt_update_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *tt_buff, uint8_t tt_num_changes, + uint8_t ttvn, uint16_t tt_crc) +{ + uint8_t orig_ttvn = (uint8_t)atomic_read(&orig_node->last_ttvn); + bool full_table = true; + struct batadv_tt_change *tt_change; + + /* don't care about a backbone gateways updates. */ + if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig)) + return; + + /* 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; + } + + tt_change = (struct batadv_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 + */ + orig_node->tt_crc = batadv_tt_global_crc(bat_priv, orig_node); + + /* 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 (orig_node->tt_crc != tt_crc) + 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 || + orig_node->tt_crc != tt_crc) { +request_table: + batadv_dbg(BATADV_DBG_TT, bat_priv, + "TT inconsistency for %pM. Need to retrieve the correct information (ttvn: %u last_ttvn: %u crc: %#.4x last_crc: %#.4x num_changes: %u)\n", + orig_node->orig, ttvn, orig_ttvn, tt_crc, + orig_node->tt_crc, tt_num_changes); + batadv_send_tt_request(bat_priv, orig_node, ttvn, + tt_crc, full_table); + return; + } + } +} + +/* returns true whether we know that the client has moved from its old + * originator to another one. This entry is kept is still kept for consistency + * purposes + */ +bool batadv_tt_global_client_is_roaming(struct batadv_priv *bat_priv, + uint8_t *addr) +{ + struct batadv_tt_global_entry *tt_global_entry; + bool ret = false; + + tt_global_entry = batadv_tt_global_hash_find(bat_priv, addr); + 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 + * + * 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) +{ + struct batadv_tt_local_entry *tt_local_entry; + bool ret = false; + + tt_local_entry = batadv_tt_local_hash_find(bat_priv, addr); + 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) +{ + bool ret = false; + + /* if the originator is a backbone node (meaning it belongs to the same + * LAN of this node) the temporary client must not be added because to + * reach such destination the node must use the LAN instead of the mesh + */ + if (batadv_bla_is_backbone_gw_orig(bat_priv, orig_node->orig)) + goto out; + + if (!batadv_tt_global_add(bat_priv, orig_node, addr, + 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 orig: %pM)\n", + addr, orig_node->orig); + ret = true; +out: + return ret; +} diff --git a/net/batman-adv/translation-table.h b/net/batman-adv/translation-table.h new file mode 100644 index 00000000..ab8e683b --- /dev/null +++ b/net/batman-adv/translation-table.h @@ -0,0 +1,68 @@ +/* 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 + */ + +#ifndef _NET_BATMAN_ADV_TRANSLATION_TABLE_H_ +#define _NET_BATMAN_ADV_TRANSLATION_TABLE_H_ + +int batadv_tt_len(int changes_num); +int batadv_tt_init(struct batadv_priv *bat_priv); +void batadv_tt_local_add(struct net_device *soft_iface, const uint8_t *addr, + int ifindex); +uint16_t batadv_tt_local_remove(struct batadv_priv *bat_priv, + const uint8_t *addr, const char *message, + bool roaming); +int batadv_tt_local_seq_print_text(struct seq_file *seq, void *offset); +void batadv_tt_global_add_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *tt_buff, int tt_buff_len); +int batadv_tt_global_add(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *addr, uint8_t flags, + uint8_t ttvn); +int batadv_tt_global_seq_print_text(struct seq_file *seq, void *offset); +void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const char *message); +struct batadv_orig_node *batadv_transtable_search(struct batadv_priv *bat_priv, + const uint8_t *src, + const uint8_t *addr); +void batadv_tt_free(struct batadv_priv *bat_priv); +bool batadv_send_tt_response(struct batadv_priv *bat_priv, + struct batadv_tt_query_packet *tt_request); +bool batadv_is_my_client(struct batadv_priv *bat_priv, const uint8_t *addr); +void batadv_handle_tt_response(struct batadv_priv *bat_priv, + struct batadv_tt_query_packet *tt_response); +bool batadv_is_ap_isolated(struct batadv_priv *bat_priv, uint8_t *src, + uint8_t *dst); +void batadv_tt_update_orig(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *tt_buff, uint8_t tt_num_changes, + uint8_t ttvn, uint16_t tt_crc); +int batadv_tt_append_diff(struct batadv_priv *bat_priv, + unsigned char **packet_buff, int *packet_buff_len, + int packet_min_len); +bool batadv_tt_global_client_is_roaming(struct batadv_priv *bat_priv, + uint8_t *addr); +bool batadv_tt_local_client_is_roaming(struct batadv_priv *bat_priv, + uint8_t *addr); +bool batadv_tt_add_temporary_global_entry(struct batadv_priv *bat_priv, + struct batadv_orig_node *orig_node, + const unsigned char *addr); + +#endif /* _NET_BATMAN_ADV_TRANSLATION_TABLE_H_ */ diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h new file mode 100644 index 00000000..aba8364c --- /dev/null +++ b/net/batman-adv/types.h @@ -0,0 +1,993 @@ +/* Copyright (C) 2007-2013 B.A.T.M.A.N. contributors: + * + * Marek Lindner, Simon Wunderlich + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_TYPES_H_ +#define _NET_BATMAN_ADV_TYPES_H_ + +#include "packet.h" +#include "bitarray.h" +#include <linux/kernel.h> + +/** + * Maximum overhead for the encapsulation for a payload packet + */ +#define BATADV_HEADER_LEN \ + (ETH_HLEN + max(sizeof(struct batadv_unicast_packet), \ + sizeof(struct batadv_bcast_packet))) + +#ifdef CONFIG_BATMAN_ADV_DAT + +/* batadv_dat_addr_t is the type used for all DHT addresses. If it is changed, + * BATADV_DAT_ADDR_MAX is changed as well. + * + * *Please be careful: batadv_dat_addr_t must be UNSIGNED* + */ +#define batadv_dat_addr_t uint16_t + +#endif /* CONFIG_BATMAN_ADV_DAT */ + +/** + * struct batadv_hard_iface_bat_iv - per hard interface B.A.T.M.A.N. IV data + * @ogm_buff: buffer holding the OGM packet + * @ogm_buff_len: length of the OGM packet buffer + * @ogm_seqno: OGM sequence number - used to identify each OGM + */ +struct batadv_hard_iface_bat_iv { + unsigned char *ogm_buff; + int ogm_buff_len; + atomic_t ogm_seqno; +}; + +/** + * struct batadv_hard_iface - network device known to batman-adv + * @list: list node for batadv_hardif_list + * @if_num: identificator of the interface + * @if_status: status of the interface for batman-adv + * @net_dev: pointer to the net_device + * @frag_seqno: last fragment sequence number sent by this interface + * @hardif_obj: kobject of the per interface sysfs "mesh" directory + * @refcount: number of contexts the object is used + * @batman_adv_ptype: packet type describing packets that should be processed by + * batman-adv for this interface + * @soft_iface: the batman-adv interface which uses this network interface + * @rcu: struct used for freeing in an RCU-safe manner + * @bat_iv: BATMAN IV specific per hard interface data + * @cleanup_work: work queue callback item for hard interface deinit + */ +struct batadv_hard_iface { + struct list_head list; + int16_t if_num; + char if_status; + struct net_device *net_dev; + atomic_t frag_seqno; + struct kobject *hardif_obj; + atomic_t refcount; + struct packet_type batman_adv_ptype; + struct net_device *soft_iface; + struct rcu_head rcu; + struct batadv_hard_iface_bat_iv bat_iv; + struct work_struct cleanup_work; +}; + +/** + * struct batadv_orig_node - structure for orig_list maintaining nodes of mesh + * @orig: originator ethernet address + * @primary_addr: hosts primary interface address + * @router: router that should be used to reach this originator + * @batadv_dat_addr_t: address of the orig node in the distributed hash + * @bcast_own: bitfield containing the number of our OGMs this orig_node + * rebroadcasted "back" to us (relative to last_real_seqno) + * @bcast_own_sum: counted result of bcast_own + * @last_seen: time when last packet from this node was received + * @bcast_seqno_reset: time when the broadcast seqno window was reset + * @batman_seqno_reset: time when the batman seqno window was reset + * @gw_flags: flags related to gateway class + * @flags: for now only VIS_SERVER flag + * @last_ttvn: last seen translation table version number + * @tt_crc: CRC of the translation table + * @tt_buff: last tt changeset this node received from the orig node + * @tt_buff_len: length of the last tt changeset this node received from the + * orig node + * @tt_buff_lock: lock that protects tt_buff and tt_buff_len + * @tt_size: number of global TT entries announced by the orig node + * @tt_initialised: bool keeping track of whether or not this node have received + * any translation table information from the orig node yet + * @last_real_seqno: last and best known sequence number + * @last_ttl: ttl of last received packet + * @bcast_bits: bitfield containing the info which payload broadcast originated + * from this orig node this host already has seen (relative to + * last_bcast_seqno) + * @last_bcast_seqno: last broadcast sequence number received by this host + * @neigh_list: list of potential next hop neighbor towards this orig node + * @frag_list: fragmentation buffer list for fragment re-assembly + * @last_frag_packet: time when last fragmented packet from this node was + * received + * @neigh_list_lock: lock protecting neigh_list, router and bonding_list + * @hash_entry: hlist node for batadv_priv::orig_hash + * @bat_priv: pointer to soft_iface this orig node belongs to + * @ogm_cnt_lock: lock protecting bcast_own, bcast_own_sum, + * neigh_node->real_bits & neigh_node->real_packet_count + * @bcast_seqno_lock: lock protecting bcast_bits & last_bcast_seqno + * @bond_candidates: how many candidates are available + * @bond_list: list of bonding candidates + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + * @in_coding_list: list of nodes this orig can hear + * @out_coding_list: list of nodes that can hear this orig + * @in_coding_list_lock: protects in_coding_list + * @out_coding_list_lock: protects out_coding_list + */ +struct batadv_orig_node { + uint8_t orig[ETH_ALEN]; + uint8_t primary_addr[ETH_ALEN]; + struct batadv_neigh_node __rcu *router; /* rcu protected pointer */ +#ifdef CONFIG_BATMAN_ADV_DAT + batadv_dat_addr_t dat_addr; +#endif + unsigned long *bcast_own; + uint8_t *bcast_own_sum; + unsigned long last_seen; + unsigned long bcast_seqno_reset; + unsigned long batman_seqno_reset; + uint8_t gw_flags; + uint8_t flags; + atomic_t last_ttvn; + uint16_t tt_crc; + unsigned char *tt_buff; + int16_t tt_buff_len; + spinlock_t tt_buff_lock; /* protects tt_buff & tt_buff_len */ + atomic_t tt_size; + bool tt_initialised; + uint32_t last_real_seqno; + uint8_t last_ttl; + DECLARE_BITMAP(bcast_bits, BATADV_TQ_LOCAL_WINDOW_SIZE); + uint32_t last_bcast_seqno; + struct hlist_head neigh_list; + struct list_head frag_list; + unsigned long last_frag_packet; + /* neigh_list_lock protects: neigh_list, router & bonding_list */ + spinlock_t neigh_list_lock; + struct hlist_node hash_entry; + struct batadv_priv *bat_priv; + /* ogm_cnt_lock protects: bcast_own, bcast_own_sum, + * neigh_node->real_bits & neigh_node->real_packet_count + */ + spinlock_t ogm_cnt_lock; + /* bcast_seqno_lock protects: bcast_bits & last_bcast_seqno */ + spinlock_t bcast_seqno_lock; + atomic_t bond_candidates; + struct list_head bond_list; + atomic_t refcount; + struct rcu_head rcu; +#ifdef CONFIG_BATMAN_ADV_NC + struct list_head in_coding_list; + struct list_head out_coding_list; + spinlock_t in_coding_list_lock; /* Protects in_coding_list */ + spinlock_t out_coding_list_lock; /* Protects out_coding_list */ +#endif +}; + +/** + * struct batadv_gw_node - structure for orig nodes announcing gw capabilities + * @list: list node for batadv_priv_gw::list + * @orig_node: pointer to corresponding orig node + * @deleted: this struct is scheduled for deletion + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +struct batadv_gw_node { + struct hlist_node list; + struct batadv_orig_node *orig_node; + unsigned long deleted; + atomic_t refcount; + struct rcu_head rcu; +}; + +/** + * struct batadv_neigh_node - structure for single hop neighbors + * @list: list node for batadv_orig_node::neigh_list + * @addr: mac address of neigh node + * @tq_recv: ring buffer of received TQ values from this neigh node + * @tq_index: ring buffer index + * @tq_avg: averaged tq of all tq values in the ring buffer (tq_recv) + * @last_ttl: last received ttl from this neigh node + * @bonding_list: list node for batadv_orig_node::bond_list + * @last_seen: when last packet via this neighbor was received + * @real_bits: bitfield containing the number of OGMs received from this neigh + * node (relative to orig_node->last_real_seqno) + * @real_packet_count: counted result of real_bits + * @orig_node: pointer to corresponding orig_node + * @if_incoming: pointer to incoming hard interface + * @lq_update_lock: lock protecting tq_recv & tq_index + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +struct batadv_neigh_node { + struct hlist_node list; + uint8_t addr[ETH_ALEN]; + uint8_t tq_recv[BATADV_TQ_GLOBAL_WINDOW_SIZE]; + uint8_t tq_index; + uint8_t tq_avg; + uint8_t last_ttl; + struct list_head bonding_list; + unsigned long last_seen; + DECLARE_BITMAP(real_bits, BATADV_TQ_LOCAL_WINDOW_SIZE); + uint8_t real_packet_count; + struct batadv_orig_node *orig_node; + struct batadv_hard_iface *if_incoming; + spinlock_t lq_update_lock; /* protects tq_recv & tq_index */ + atomic_t refcount; + struct rcu_head rcu; +}; + +/** + * struct batadv_bcast_duplist_entry - structure for LAN broadcast suppression + * @orig[ETH_ALEN]: mac address of orig node orginating the broadcast + * @crc: crc32 checksum of broadcast payload + * @entrytime: time when the broadcast packet was received + */ +#ifdef CONFIG_BATMAN_ADV_BLA +struct batadv_bcast_duplist_entry { + uint8_t orig[ETH_ALEN]; + __be32 crc; + unsigned long entrytime; +}; +#endif + +/** + * enum batadv_counters - indices for traffic counters + * @BATADV_CNT_TX: transmitted payload traffic packet counter + * @BATADV_CNT_TX_BYTES: transmitted payload traffic bytes counter + * @BATADV_CNT_TX_DROPPED: dropped transmission payload traffic packet counter + * @BATADV_CNT_RX: received payload traffic packet counter + * @BATADV_CNT_RX_BYTES: received payload traffic bytes counter + * @BATADV_CNT_FORWARD: forwarded payload traffic packet counter + * @BATADV_CNT_FORWARD_BYTES: forwarded payload traffic bytes counter + * @BATADV_CNT_MGMT_TX: transmitted routing protocol traffic packet counter + * @BATADV_CNT_MGMT_TX_BYTES: transmitted routing protocol traffic bytes counter + * @BATADV_CNT_MGMT_RX: received routing protocol traffic packet counter + * @BATADV_CNT_MGMT_RX_BYTES: received routing protocol traffic bytes counter + * @BATADV_CNT_TT_REQUEST_TX: transmitted tt req traffic packet counter + * @BATADV_CNT_TT_REQUEST_RX: received tt req traffic packet counter + * @BATADV_CNT_TT_RESPONSE_TX: transmitted tt resp traffic packet counter + * @BATADV_CNT_TT_RESPONSE_RX: received tt resp traffic packet counter + * @BATADV_CNT_TT_ROAM_ADV_TX: transmitted tt roam traffic packet counter + * @BATADV_CNT_TT_ROAM_ADV_RX: received tt roam traffic packet counter + * @BATADV_CNT_DAT_GET_TX: transmitted dht GET traffic packet counter + * @BATADV_CNT_DAT_GET_RX: received dht GET traffic packet counter + * @BATADV_CNT_DAT_PUT_TX: transmitted dht PUT traffic packet counter + * @BATADV_CNT_DAT_PUT_RX: received dht PUT traffic packet counter + * @BATADV_CNT_DAT_CACHED_REPLY_TX: transmitted dat cache reply traffic packet + * counter + * @BATADV_CNT_NC_CODE: transmitted nc-combined traffic packet counter + * @BATADV_CNT_NC_CODE_BYTES: transmitted nc-combined traffic bytes counter + * @BATADV_CNT_NC_RECODE: transmitted nc-recombined traffic packet counter + * @BATADV_CNT_NC_RECODE_BYTES: transmitted nc-recombined traffic bytes counter + * @BATADV_CNT_NC_BUFFER: counter for packets buffered for later nc decoding + * @BATADV_CNT_NC_DECODE: received and nc-decoded traffic packet counter + * @BATADV_CNT_NC_DECODE_BYTES: received and nc-decoded traffic bytes counter + * @BATADV_CNT_NC_DECODE_FAILED: received and decode-failed traffic packet + * counter + * @BATADV_CNT_NC_SNIFFED: counter for nc-decoded packets received in promisc + * mode. + * @BATADV_CNT_NUM: number of traffic counters + */ +enum batadv_counters { + BATADV_CNT_TX, + BATADV_CNT_TX_BYTES, + BATADV_CNT_TX_DROPPED, + BATADV_CNT_RX, + BATADV_CNT_RX_BYTES, + BATADV_CNT_FORWARD, + BATADV_CNT_FORWARD_BYTES, + BATADV_CNT_MGMT_TX, + BATADV_CNT_MGMT_TX_BYTES, + BATADV_CNT_MGMT_RX, + BATADV_CNT_MGMT_RX_BYTES, + BATADV_CNT_TT_REQUEST_TX, + BATADV_CNT_TT_REQUEST_RX, + BATADV_CNT_TT_RESPONSE_TX, + BATADV_CNT_TT_RESPONSE_RX, + BATADV_CNT_TT_ROAM_ADV_TX, + BATADV_CNT_TT_ROAM_ADV_RX, +#ifdef CONFIG_BATMAN_ADV_DAT + BATADV_CNT_DAT_GET_TX, + BATADV_CNT_DAT_GET_RX, + BATADV_CNT_DAT_PUT_TX, + BATADV_CNT_DAT_PUT_RX, + BATADV_CNT_DAT_CACHED_REPLY_TX, +#endif +#ifdef CONFIG_BATMAN_ADV_NC + BATADV_CNT_NC_CODE, + BATADV_CNT_NC_CODE_BYTES, + BATADV_CNT_NC_RECODE, + BATADV_CNT_NC_RECODE_BYTES, + BATADV_CNT_NC_BUFFER, + BATADV_CNT_NC_DECODE, + BATADV_CNT_NC_DECODE_BYTES, + BATADV_CNT_NC_DECODE_FAILED, + BATADV_CNT_NC_SNIFFED, +#endif + BATADV_CNT_NUM, +}; + +/** + * struct batadv_priv_tt - per mesh interface translation table data + * @vn: translation table version number + * @ogm_append_cnt: counter of number of OGMs containing the local tt diff + * @local_changes: changes registered in an originator interval + * @changes_list: tracks tt local changes within an originator interval + * @local_hash: local translation table hash table + * @global_hash: global translation table hash table + * @req_list: list of pending & unanswered tt_requests + * @roam_list: list of the last roaming events of each client limiting the + * number of roaming events to avoid route flapping + * @changes_list_lock: lock protecting changes_list + * @req_list_lock: lock protecting req_list + * @roam_list_lock: lock protecting roam_list + * @local_entry_num: number of entries in the local hash table + * @local_crc: Checksum of the local table, recomputed before sending a new OGM + * @last_changeset: last tt changeset this host has generated + * @last_changeset_len: length of last tt changeset this host has generated + * @last_changeset_lock: lock protecting last_changeset & last_changeset_len + * @work: work queue callback item for translation table purging + */ +struct batadv_priv_tt { + atomic_t vn; + atomic_t ogm_append_cnt; + atomic_t local_changes; + struct list_head changes_list; + struct batadv_hashtable *local_hash; + struct batadv_hashtable *global_hash; + struct list_head req_list; + struct list_head roam_list; + spinlock_t changes_list_lock; /* protects changes */ + spinlock_t req_list_lock; /* protects req_list */ + spinlock_t roam_list_lock; /* protects roam_list */ + atomic_t local_entry_num; + uint16_t local_crc; + unsigned char *last_changeset; + int16_t last_changeset_len; + /* protects last_changeset & last_changeset_len */ + spinlock_t last_changeset_lock; + struct delayed_work work; +}; + +/** + * struct batadv_priv_bla - per mesh interface bridge loope avoidance data + * @num_requests; number of bla requests in flight + * @claim_hash: hash table containing mesh nodes this host has claimed + * @backbone_hash: hash table containing all detected backbone gateways + * @bcast_duplist: recently received broadcast packets array (for broadcast + * duplicate suppression) + * @bcast_duplist_curr: index of last broadcast packet added to bcast_duplist + * @bcast_duplist_lock: lock protecting bcast_duplist & bcast_duplist_curr + * @claim_dest: local claim data (e.g. claim group) + * @work: work queue callback item for cleanups & bla announcements + */ +#ifdef CONFIG_BATMAN_ADV_BLA +struct batadv_priv_bla { + atomic_t num_requests; + struct batadv_hashtable *claim_hash; + struct batadv_hashtable *backbone_hash; + struct batadv_bcast_duplist_entry bcast_duplist[BATADV_DUPLIST_SIZE]; + int bcast_duplist_curr; + /* protects bcast_duplist & bcast_duplist_curr */ + spinlock_t bcast_duplist_lock; + struct batadv_bla_claim_dst claim_dest; + struct delayed_work work; +}; +#endif + +/** + * struct batadv_debug_log - debug logging data + * @log_buff: buffer holding the logs (ring bufer) + * @log_start: index of next character to read + * @log_end: index of next character to write + * @lock: lock protecting log_buff, log_start & log_end + * @queue_wait: log reader's wait queue + */ +#ifdef CONFIG_BATMAN_ADV_DEBUG +struct batadv_priv_debug_log { + char log_buff[BATADV_LOG_BUF_LEN]; + unsigned long log_start; + unsigned long log_end; + spinlock_t lock; /* protects log_buff, log_start and log_end */ + wait_queue_head_t queue_wait; +}; +#endif + +/** + * struct batadv_priv_gw - per mesh interface gateway data + * @list: list of available gateway nodes + * @list_lock: lock protecting gw_list & curr_gw + * @curr_gw: pointer to currently selected gateway node + * @reselect: bool indicating a gateway re-selection is in progress + */ +struct batadv_priv_gw { + struct hlist_head list; + spinlock_t list_lock; /* protects gw_list & curr_gw */ + struct batadv_gw_node __rcu *curr_gw; /* rcu protected pointer */ + atomic_t reselect; +}; + +/** + * struct batadv_priv_vis - per mesh interface vis data + * @send_list: list of batadv_vis_info packets to sent + * @hash: hash table containing vis data from other nodes in the network + * @hash_lock: lock protecting the hash table + * @list_lock: lock protecting my_info::recv_list + * @work: work queue callback item for vis packet sending + * @my_info: holds this node's vis data sent on a regular basis + */ +struct batadv_priv_vis { + struct list_head send_list; + struct batadv_hashtable *hash; + spinlock_t hash_lock; /* protects hash */ + spinlock_t list_lock; /* protects my_info::recv_list */ + struct delayed_work work; + struct batadv_vis_info *my_info; +}; + +/** + * struct batadv_priv_dat - per mesh interface DAT private data + * @addr: node DAT address + * @hash: hashtable representing the local ARP cache + * @work: work queue callback item for cache purging + */ +#ifdef CONFIG_BATMAN_ADV_DAT +struct batadv_priv_dat { + batadv_dat_addr_t addr; + struct batadv_hashtable *hash; + struct delayed_work work; +}; +#endif + +/** + * struct batadv_priv_nc - per mesh interface network coding private data + * @work: work queue callback item for cleanup + * @debug_dir: dentry for nc subdir in batman-adv directory in debugfs + * @min_tq: only consider neighbors for encoding if neigh_tq > min_tq + * @max_fwd_delay: maximum packet forward delay to allow coding of packets + * @max_buffer_time: buffer time for sniffed packets used to decoding + * @timestamp_fwd_flush: timestamp of last forward packet queue flush + * @timestamp_sniffed_purge: timestamp of last sniffed packet queue purge + * @coding_hash: Hash table used to buffer skbs while waiting for another + * incoming skb to code it with. Skbs are added to the buffer just before being + * forwarded in routing.c + * @decoding_hash: Hash table used to buffer skbs that might be needed to decode + * a received coded skb. The buffer is used for 1) skbs arriving on the + * soft-interface; 2) skbs overheard on the hard-interface; and 3) skbs + * forwarded by batman-adv. + */ +struct batadv_priv_nc { + struct delayed_work work; + struct dentry *debug_dir; + u8 min_tq; + u32 max_fwd_delay; + u32 max_buffer_time; + unsigned long timestamp_fwd_flush; + unsigned long timestamp_sniffed_purge; + struct batadv_hashtable *coding_hash; + struct batadv_hashtable *decoding_hash; +}; + +/** + * struct batadv_priv - per mesh interface data + * @mesh_state: current status of the mesh (inactive/active/deactivating) + * @soft_iface: net device which holds this struct as private data + * @stats: structure holding the data for the ndo_get_stats() call + * @bat_counters: mesh internal traffic statistic counters (see batadv_counters) + * @aggregated_ogms: bool indicating whether OGM aggregation is enabled + * @bonding: bool indicating whether traffic bonding is enabled + * @fragmentation: bool indicating whether traffic fragmentation is enabled + * @ap_isolation: bool indicating whether ap isolation is enabled + * @bridge_loop_avoidance: bool indicating whether bridge loop avoidance is + * enabled + * @distributed_arp_table: bool indicating whether distributed ARP table is + * enabled + * @vis_mode: vis operation: client or server (see batadv_vis_packettype) + * @gw_mode: gateway operation: off, client or server (see batadv_gw_modes) + * @gw_sel_class: gateway selection class (applies if gw_mode client) + * @gw_bandwidth: gateway announced bandwidth (applies if gw_mode server) + * @orig_interval: OGM broadcast interval in milliseconds + * @hop_penalty: penalty which will be applied to an OGM's tq-field on every hop + * @log_level: configured log level (see batadv_dbg_level) + * @bcast_seqno: last sent broadcast packet sequence number + * @bcast_queue_left: number of remaining buffered broadcast packet slots + * @batman_queue_left: number of remaining OGM packet slots + * @num_ifaces: number of interfaces assigned to this mesh interface + * @mesh_obj: kobject for sysfs mesh subdirectory + * @debug_dir: dentry for debugfs batman-adv subdirectory + * @forw_bat_list: list of aggregated OGMs that will be forwarded + * @forw_bcast_list: list of broadcast packets that will be rebroadcasted + * @orig_hash: hash table containing mesh participants (orig nodes) + * @forw_bat_list_lock: lock protecting forw_bat_list + * @forw_bcast_list_lock: lock protecting forw_bcast_list + * @orig_work: work queue callback item for orig node purging + * @cleanup_work: work queue callback item for soft interface deinit + * @primary_if: one of the hard interfaces assigned to this mesh interface + * becomes the primary interface + * @bat_algo_ops: routing algorithm used by this mesh interface + * @bla: bridge loope avoidance data + * @debug_log: holding debug logging relevant data + * @gw: gateway data + * @tt: translation table data + * @vis: vis data + * @dat: distributed arp table data + * @network_coding: bool indicating whether network coding is enabled + * @batadv_priv_nc: network coding data + */ +struct batadv_priv { + atomic_t mesh_state; + struct net_device *soft_iface; + struct net_device_stats stats; + uint64_t __percpu *bat_counters; /* Per cpu counters */ + atomic_t aggregated_ogms; + atomic_t bonding; + atomic_t fragmentation; + atomic_t ap_isolation; +#ifdef CONFIG_BATMAN_ADV_BLA + atomic_t bridge_loop_avoidance; +#endif +#ifdef CONFIG_BATMAN_ADV_DAT + atomic_t distributed_arp_table; +#endif + atomic_t vis_mode; + atomic_t gw_mode; + atomic_t gw_sel_class; + atomic_t gw_bandwidth; + atomic_t orig_interval; + atomic_t hop_penalty; +#ifdef CONFIG_BATMAN_ADV_DEBUG + atomic_t log_level; +#endif + atomic_t bcast_seqno; + atomic_t bcast_queue_left; + atomic_t batman_queue_left; + char num_ifaces; + struct kobject *mesh_obj; + struct dentry *debug_dir; + struct hlist_head forw_bat_list; + struct hlist_head forw_bcast_list; + struct batadv_hashtable *orig_hash; + spinlock_t forw_bat_list_lock; /* protects forw_bat_list */ + spinlock_t forw_bcast_list_lock; /* protects forw_bcast_list */ + struct delayed_work orig_work; + struct work_struct cleanup_work; + struct batadv_hard_iface __rcu *primary_if; /* rcu protected pointer */ + struct batadv_algo_ops *bat_algo_ops; +#ifdef CONFIG_BATMAN_ADV_BLA + struct batadv_priv_bla bla; +#endif +#ifdef CONFIG_BATMAN_ADV_DEBUG + struct batadv_priv_debug_log *debug_log; +#endif + struct batadv_priv_gw gw; + struct batadv_priv_tt tt; + struct batadv_priv_vis vis; +#ifdef CONFIG_BATMAN_ADV_DAT + struct batadv_priv_dat dat; +#endif +#ifdef CONFIG_BATMAN_ADV_NC + atomic_t network_coding; + struct batadv_priv_nc nc; +#endif /* CONFIG_BATMAN_ADV_NC */ +}; + +/** + * struct batadv_socket_client - layer2 icmp socket client data + * @queue_list: packet queue for packets destined for this socket client + * @queue_len: number of packets in the packet queue (queue_list) + * @index: socket client's index in the batadv_socket_client_hash + * @lock: lock protecting queue_list, queue_len & index + * @queue_wait: socket client's wait queue + * @bat_priv: pointer to soft_iface this client belongs to + */ +struct batadv_socket_client { + struct list_head queue_list; + unsigned int queue_len; + unsigned char index; + spinlock_t lock; /* protects queue_list, queue_len & index */ + wait_queue_head_t queue_wait; + struct batadv_priv *bat_priv; +}; + +/** + * struct batadv_socket_packet - layer2 icmp packet for socket client + * @list: list node for batadv_socket_client::queue_list + * @icmp_len: size of the layer2 icmp packet + * @icmp_packet: layer2 icmp packet + */ +struct batadv_socket_packet { + struct list_head list; + size_t icmp_len; + struct batadv_icmp_packet_rr icmp_packet; +}; + +/** + * struct batadv_bla_backbone_gw - batman-adv gateway bridged into the LAN + * @orig: originator address of backbone node (mac address of primary iface) + * @vid: vlan id this gateway was detected on + * @hash_entry: hlist node for batadv_priv_bla::backbone_hash + * @bat_priv: pointer to soft_iface this backbone gateway belongs to + * @lasttime: last time we heard of this backbone gw + * @wait_periods: grace time for bridge forward delays and bla group forming at + * bootup phase - no bcast traffic is formwared until it has elapsed + * @request_sent: if this bool is set to true we are out of sync with this + * backbone gateway - no bcast traffic is formwared until the situation was + * resolved + * @crc: crc16 checksum over all claims + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +#ifdef CONFIG_BATMAN_ADV_BLA +struct batadv_bla_backbone_gw { + uint8_t orig[ETH_ALEN]; + short vid; + struct hlist_node hash_entry; + struct batadv_priv *bat_priv; + unsigned long lasttime; + atomic_t wait_periods; + atomic_t request_sent; + uint16_t crc; + atomic_t refcount; + struct rcu_head rcu; +}; + +/** + * struct batadv_bla_claim - claimed non-mesh client structure + * @addr: mac address of claimed non-mesh client + * @vid: vlan id this client was detected on + * @batadv_bla_backbone_gw: pointer to backbone gw claiming this client + * @lasttime: last time we heard of claim (locals only) + * @hash_entry: hlist node for batadv_priv_bla::claim_hash + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +struct batadv_bla_claim { + uint8_t addr[ETH_ALEN]; + short vid; + struct batadv_bla_backbone_gw *backbone_gw; + unsigned long lasttime; + struct hlist_node hash_entry; + struct rcu_head rcu; + atomic_t refcount; +}; +#endif + +/** + * struct batadv_tt_common_entry - tt local & tt global common data + * @addr: mac address of non-mesh client + * @hash_entry: hlist node for batadv_priv_tt::local_hash or for + * batadv_priv_tt::global_hash + * @flags: various state handling flags (see batadv_tt_client_flags) + * @added_at: timestamp used for purging stale tt common entries + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +struct batadv_tt_common_entry { + uint8_t addr[ETH_ALEN]; + struct hlist_node hash_entry; + uint16_t flags; + unsigned long added_at; + atomic_t refcount; + struct rcu_head rcu; +}; + +/** + * struct batadv_tt_local_entry - translation table local entry data + * @common: general translation table data + * @last_seen: timestamp used for purging stale tt local entries + */ +struct batadv_tt_local_entry { + struct batadv_tt_common_entry common; + unsigned long last_seen; +}; + +/** + * struct batadv_tt_global_entry - translation table global entry data + * @common: general translation table data + * @orig_list: list of orig nodes announcing this non-mesh client + * @list_lock: lock protecting orig_list + * @roam_at: time at which TT_GLOBAL_ROAM was set + */ +struct batadv_tt_global_entry { + struct batadv_tt_common_entry common; + struct hlist_head orig_list; + spinlock_t list_lock; /* protects orig_list */ + unsigned long roam_at; +}; + +/** + * struct batadv_tt_orig_list_entry - orig node announcing a non-mesh client + * @orig_node: pointer to orig node announcing this non-mesh client + * @ttvn: translation table version number which added the non-mesh client + * @list: list node for batadv_tt_global_entry::orig_list + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +struct batadv_tt_orig_list_entry { + struct batadv_orig_node *orig_node; + uint8_t ttvn; + struct hlist_node list; + atomic_t refcount; + struct rcu_head rcu; +}; + +/** + * struct batadv_tt_change_node - structure for tt changes occured + * @list: list node for batadv_priv_tt::changes_list + * @change: holds the actual translation table diff data + */ +struct batadv_tt_change_node { + struct list_head list; + struct batadv_tt_change change; +}; + +/** + * struct batadv_tt_req_node - data to keep track of the tt requests in flight + * @addr: mac address address of the originator this request was sent to + * @issued_at: timestamp used for purging stale tt requests + * @list: list node for batadv_priv_tt::req_list + */ +struct batadv_tt_req_node { + uint8_t addr[ETH_ALEN]; + unsigned long issued_at; + struct list_head list; +}; + +/** + * struct batadv_tt_roam_node - roaming client data + * @addr: mac address of the client in the roaming phase + * @counter: number of allowed roaming events per client within a single + * OGM interval (changes are committed with each OGM) + * @first_time: timestamp used for purging stale roaming node entries + * @list: list node for batadv_priv_tt::roam_list + */ +struct batadv_tt_roam_node { + uint8_t addr[ETH_ALEN]; + atomic_t counter; + unsigned long first_time; + struct list_head list; +}; + +/** + * struct batadv_nc_node - network coding node + * @list: next and prev pointer for the list handling + * @addr: the node's mac address + * @refcount: number of contexts the object is used by + * @rcu: struct used for freeing in an RCU-safe manner + * @orig_node: pointer to corresponding orig node struct + * @last_seen: timestamp of last ogm received from this node + */ +struct batadv_nc_node { + struct list_head list; + uint8_t addr[ETH_ALEN]; + atomic_t refcount; + struct rcu_head rcu; + struct batadv_orig_node *orig_node; + unsigned long last_seen; +}; + +/** + * struct batadv_nc_path - network coding path + * @hash_entry: next and prev pointer for the list handling + * @rcu: struct used for freeing in an RCU-safe manner + * @refcount: number of contexts the object is used by + * @packet_list: list of buffered packets for this path + * @packet_list_lock: access lock for packet list + * @next_hop: next hop (destination) of path + * @prev_hop: previous hop (source) of path + * @last_valid: timestamp for last validation of path + */ +struct batadv_nc_path { + struct hlist_node hash_entry; + struct rcu_head rcu; + atomic_t refcount; + struct list_head packet_list; + spinlock_t packet_list_lock; /* Protects packet_list */ + uint8_t next_hop[ETH_ALEN]; + uint8_t prev_hop[ETH_ALEN]; + unsigned long last_valid; +}; + +/** + * struct batadv_nc_packet - network coding packet used when coding and + * decoding packets + * @list: next and prev pointer for the list handling + * @packet_id: crc32 checksum of skb data + * @timestamp: field containing the info when the packet was added to path + * @neigh_node: pointer to original next hop neighbor of skb + * @skb: skb which can be encoded or used for decoding + * @nc_path: pointer to path this nc packet is attached to + */ +struct batadv_nc_packet { + struct list_head list; + __be32 packet_id; + unsigned long timestamp; + struct batadv_neigh_node *neigh_node; + struct sk_buff *skb; + struct batadv_nc_path *nc_path; +}; + +/** + * batadv_skb_cb - control buffer structure used to store private data relevant + * to batman-adv in the skb->cb buffer in skbs. + * @decoded: Marks a skb as decoded, which is checked when searching for coding + * opportunities in network-coding.c + */ +struct batadv_skb_cb { + bool decoded; +}; + +/** + * struct batadv_forw_packet - structure for bcast packets to be sent/forwarded + * @list: list node for batadv_socket_client::queue_list + * @send_time: execution time for delayed_work (packet sending) + * @own: bool for locally generated packets (local OGMs are re-scheduled after + * sending) + * @skb: bcast packet's skb buffer + * @packet_len: size of aggregated OGM packet inside the skb buffer + * @direct_link_flags: direct link flags for aggregated OGM packets + * @num_packets: counter for bcast packet retransmission + * @delayed_work: work queue callback item for packet sending + * @if_incoming: pointer incoming hard-iface or primary iface if locally + * generated packet + */ +struct batadv_forw_packet { + struct hlist_node list; + unsigned long send_time; + uint8_t own; + struct sk_buff *skb; + uint16_t packet_len; + uint32_t direct_link_flags; + uint8_t num_packets; + struct delayed_work delayed_work; + struct batadv_hard_iface *if_incoming; +}; + +/** + * struct batadv_frag_packet_list_entry - storage for fragment packet + * @list: list node for orig_node::frag_list + * @seqno: sequence number of the fragment + * @skb: fragment's skb buffer + */ +struct batadv_frag_packet_list_entry { + struct list_head list; + uint16_t seqno; + struct sk_buff *skb; +}; + +/** + * struct batadv_vis_info - local data for vis information + * @first_seen: timestamp used for purging stale vis info entries + * @recv_list: List of server-neighbors we have received this packet from. This + * packet should not be re-forward to them again. List elements are struct + * batadv_vis_recvlist_node + * @send_list: list of packets to be forwarded + * @refcount: number of contexts the object is used + * @hash_entry: hlist node for batadv_priv_vis::hash + * @bat_priv: pointer to soft_iface this orig node belongs to + * @skb_packet: contains the vis packet + */ +struct batadv_vis_info { + unsigned long first_seen; + struct list_head recv_list; + struct list_head send_list; + struct kref refcount; + struct hlist_node hash_entry; + struct batadv_priv *bat_priv; + struct sk_buff *skb_packet; +} __packed; + +/** + * struct batadv_vis_info_entry - contains link information for vis + * @src: source MAC of the link, all zero for local TT entry + * @dst: destination MAC of the link, client mac address for local TT entry + * @quality: transmission quality of the link, or 0 for local TT entry + */ +struct batadv_vis_info_entry { + uint8_t src[ETH_ALEN]; + uint8_t dest[ETH_ALEN]; + uint8_t quality; +} __packed; + +/** + * struct batadv_vis_recvlist_node - list entry for batadv_vis_info::recv_list + * @list: list node for batadv_vis_info::recv_list + * @mac: MAC address of the originator from where the vis_info was received + */ +struct batadv_vis_recvlist_node { + struct list_head list; + uint8_t mac[ETH_ALEN]; +}; + +/** + * struct batadv_vis_if_list_entry - auxiliary data for vis data generation + * @addr: MAC address of the interface + * @primary: true if this interface is the primary interface + * @list: list node the interface list + * + * While scanning for vis-entries of a particular vis-originator + * this list collects its interfaces to create a subgraph/cluster + * out of them later + */ +struct batadv_vis_if_list_entry { + uint8_t addr[ETH_ALEN]; + bool primary; + struct hlist_node list; +}; + +/** + * struct batadv_algo_ops - mesh algorithm callbacks + * @list: list node for the batadv_algo_list + * @name: name of the algorithm + * @bat_iface_enable: init routing info when hard-interface is enabled + * @bat_iface_disable: de-init routing info when hard-interface is disabled + * @bat_iface_update_mac: (re-)init mac addresses of the protocol information + * belonging to this hard-interface + * @bat_primary_iface_set: called when primary interface is selected / changed + * @bat_ogm_schedule: prepare a new outgoing OGM for the send queue + * @bat_ogm_emit: send scheduled OGM + */ +struct batadv_algo_ops { + struct hlist_node list; + char *name; + int (*bat_iface_enable)(struct batadv_hard_iface *hard_iface); + void (*bat_iface_disable)(struct batadv_hard_iface *hard_iface); + void (*bat_iface_update_mac)(struct batadv_hard_iface *hard_iface); + void (*bat_primary_iface_set)(struct batadv_hard_iface *hard_iface); + void (*bat_ogm_schedule)(struct batadv_hard_iface *hard_iface); + void (*bat_ogm_emit)(struct batadv_forw_packet *forw_packet); +}; + +/** + * struct batadv_dat_entry - it is a single entry of batman-adv ARP backend. It + * is used to stored ARP entries needed for the global DAT cache + * @ip: the IPv4 corresponding to this DAT/ARP entry + * @mac_addr: the MAC address associated to the stored IPv4 + * @last_update: time in jiffies when this entry was refreshed last time + * @hash_entry: hlist node for batadv_priv_dat::hash + * @refcount: number of contexts the object is used + * @rcu: struct used for freeing in an RCU-safe manner + */ +struct batadv_dat_entry { + __be32 ip; + uint8_t mac_addr[ETH_ALEN]; + unsigned long last_update; + struct hlist_node hash_entry; + atomic_t refcount; + struct rcu_head rcu; +}; + +/** + * struct batadv_dat_candidate - candidate destination for DAT operations + * @type: the type of the selected candidate. It can one of the following: + * - BATADV_DAT_CANDIDATE_NOT_FOUND + * - BATADV_DAT_CANDIDATE_ORIG + * @orig_node: if type is BATADV_DAT_CANDIDATE_ORIG this field points to the + * corresponding originator node structure + */ +struct batadv_dat_candidate { + int type; + struct batadv_orig_node *orig_node; +}; + +#endif /* _NET_BATMAN_ADV_TYPES_H_ */ diff --git a/net/batman-adv/unicast.c b/net/batman-adv/unicast.c new file mode 100644 index 00000000..0bb3b598 --- /dev/null +++ b/net/batman-adv/unicast.c @@ -0,0 +1,478 @@ +/* Copyright (C) 2010-2013 B.A.T.M.A.N. contributors: + * + * Andreas Langer + * + * 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 "unicast.h" +#include "send.h" +#include "soft-interface.h" +#include "gateway_client.h" +#include "originator.h" +#include "hash.h" +#include "translation-table.h" +#include "routing.h" +#include "hard-interface.h" + + +static struct sk_buff * +batadv_frag_merge_packet(struct list_head *head, + struct batadv_frag_packet_list_entry *tfp, + struct sk_buff *skb) +{ + struct batadv_unicast_frag_packet *up; + struct sk_buff *tmp_skb; + struct batadv_unicast_packet *unicast_packet; + int hdr_len = sizeof(*unicast_packet); + int uni_diff = sizeof(*up) - hdr_len; + uint8_t *packet_pos; + + up = (struct batadv_unicast_frag_packet *)skb->data; + /* set skb to the first part and tmp_skb to the second part */ + if (up->flags & BATADV_UNI_FRAG_HEAD) { + tmp_skb = tfp->skb; + } else { + tmp_skb = skb; + skb = tfp->skb; + } + + if (skb_linearize(skb) < 0 || skb_linearize(tmp_skb) < 0) + goto err; + + skb_pull(tmp_skb, sizeof(*up)); + if (pskb_expand_head(skb, 0, tmp_skb->len, GFP_ATOMIC) < 0) + goto err; + + /* move free entry to end */ + tfp->skb = NULL; + tfp->seqno = 0; + list_move_tail(&tfp->list, head); + + memcpy(skb_put(skb, tmp_skb->len), tmp_skb->data, tmp_skb->len); + kfree_skb(tmp_skb); + + memmove(skb->data + uni_diff, skb->data, hdr_len); + packet_pos = skb_pull(skb, uni_diff); + unicast_packet = (struct batadv_unicast_packet *)packet_pos; + unicast_packet->header.packet_type = BATADV_UNICAST; + + return skb; + +err: + /* free buffered skb, skb will be freed later */ + kfree_skb(tfp->skb); + return NULL; +} + +static void batadv_frag_create_entry(struct list_head *head, + struct sk_buff *skb) +{ + struct batadv_frag_packet_list_entry *tfp; + struct batadv_unicast_frag_packet *up; + + up = (struct batadv_unicast_frag_packet *)skb->data; + + /* free and oldest packets stand at the end */ + tfp = list_entry((head)->prev, typeof(*tfp), list); + kfree_skb(tfp->skb); + + tfp->seqno = ntohs(up->seqno); + tfp->skb = skb; + list_move(&tfp->list, head); + return; +} + +static int batadv_frag_create_buffer(struct list_head *head) +{ + int i; + struct batadv_frag_packet_list_entry *tfp; + + for (i = 0; i < BATADV_FRAG_BUFFER_SIZE; i++) { + tfp = kmalloc(sizeof(*tfp), GFP_ATOMIC); + if (!tfp) { + batadv_frag_list_free(head); + return -ENOMEM; + } + tfp->skb = NULL; + tfp->seqno = 0; + INIT_LIST_HEAD(&tfp->list); + list_add(&tfp->list, head); + } + + return 0; +} + +static struct batadv_frag_packet_list_entry * +batadv_frag_search_packet(struct list_head *head, + const struct batadv_unicast_frag_packet *up) +{ + struct batadv_frag_packet_list_entry *tfp; + struct batadv_unicast_frag_packet *tmp_up = NULL; + bool is_head_tmp, is_head; + uint16_t search_seqno; + + if (up->flags & BATADV_UNI_FRAG_HEAD) + search_seqno = ntohs(up->seqno)+1; + else + search_seqno = ntohs(up->seqno)-1; + + is_head = up->flags & BATADV_UNI_FRAG_HEAD; + + list_for_each_entry(tfp, head, list) { + if (!tfp->skb) + continue; + + if (tfp->seqno == ntohs(up->seqno)) + goto mov_tail; + + tmp_up = (struct batadv_unicast_frag_packet *)tfp->skb->data; + + if (tfp->seqno == search_seqno) { + is_head_tmp = tmp_up->flags & BATADV_UNI_FRAG_HEAD; + if (is_head_tmp != is_head) + return tfp; + else + goto mov_tail; + } + } + return NULL; + +mov_tail: + list_move_tail(&tfp->list, head); + return NULL; +} + +void batadv_frag_list_free(struct list_head *head) +{ + struct batadv_frag_packet_list_entry *pf, *tmp_pf; + + if (!list_empty(head)) { + list_for_each_entry_safe(pf, tmp_pf, head, list) { + kfree_skb(pf->skb); + list_del(&pf->list); + kfree(pf); + } + } + return; +} + +/* frag_reassemble_skb(): + * returns NET_RX_DROP if the operation failed - skb is left intact + * returns NET_RX_SUCCESS if the fragment was buffered (skb_new will be NULL) + * or the skb could be reassembled (skb_new will point to the new packet and + * skb was freed) + */ +int batadv_frag_reassemble_skb(struct sk_buff *skb, + struct batadv_priv *bat_priv, + struct sk_buff **new_skb) +{ + struct batadv_orig_node *orig_node; + struct batadv_frag_packet_list_entry *tmp_frag_entry; + int ret = NET_RX_DROP; + struct batadv_unicast_frag_packet *unicast_packet; + + unicast_packet = (struct batadv_unicast_frag_packet *)skb->data; + *new_skb = NULL; + + orig_node = batadv_orig_hash_find(bat_priv, unicast_packet->orig); + if (!orig_node) + goto out; + + orig_node->last_frag_packet = jiffies; + + if (list_empty(&orig_node->frag_list) && + batadv_frag_create_buffer(&orig_node->frag_list)) { + pr_debug("couldn't create frag buffer\n"); + goto out; + } + + tmp_frag_entry = batadv_frag_search_packet(&orig_node->frag_list, + unicast_packet); + + if (!tmp_frag_entry) { + batadv_frag_create_entry(&orig_node->frag_list, skb); + ret = NET_RX_SUCCESS; + goto out; + } + + *new_skb = batadv_frag_merge_packet(&orig_node->frag_list, + tmp_frag_entry, skb); + /* if not, merge failed */ + if (*new_skb) + ret = NET_RX_SUCCESS; + +out: + if (orig_node) + batadv_orig_node_free_ref(orig_node); + return ret; +} + +int batadv_frag_send_skb(struct sk_buff *skb, struct batadv_priv *bat_priv, + struct batadv_hard_iface *hard_iface, + const uint8_t dstaddr[]) +{ + struct batadv_unicast_packet tmp_uc, *unicast_packet; + struct batadv_hard_iface *primary_if; + struct sk_buff *frag_skb; + struct batadv_unicast_frag_packet *frag1, *frag2; + int uc_hdr_len = sizeof(*unicast_packet); + int ucf_hdr_len = sizeof(*frag1); + int data_len = skb->len - uc_hdr_len; + int large_tail = 0, ret = NET_RX_DROP; + uint16_t seqno; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto dropped; + + frag_skb = dev_alloc_skb(data_len - (data_len / 2) + ucf_hdr_len); + if (!frag_skb) + goto dropped; + skb_reserve(frag_skb, ucf_hdr_len); + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + memcpy(&tmp_uc, unicast_packet, uc_hdr_len); + skb_split(skb, frag_skb, data_len / 2 + uc_hdr_len); + + if (batadv_skb_head_push(skb, ucf_hdr_len - uc_hdr_len) < 0 || + batadv_skb_head_push(frag_skb, ucf_hdr_len) < 0) + goto drop_frag; + + frag1 = (struct batadv_unicast_frag_packet *)skb->data; + frag2 = (struct batadv_unicast_frag_packet *)frag_skb->data; + + memcpy(frag1, &tmp_uc, sizeof(tmp_uc)); + + frag1->header.ttl--; + frag1->header.version = BATADV_COMPAT_VERSION; + frag1->header.packet_type = BATADV_UNICAST_FRAG; + + memcpy(frag1->orig, primary_if->net_dev->dev_addr, ETH_ALEN); + memcpy(frag2, frag1, sizeof(*frag2)); + + if (data_len & 1) + large_tail = BATADV_UNI_FRAG_LARGETAIL; + + frag1->flags = BATADV_UNI_FRAG_HEAD | large_tail; + frag2->flags = large_tail; + + seqno = atomic_add_return(2, &hard_iface->frag_seqno); + frag1->seqno = htons(seqno - 1); + frag2->seqno = htons(seqno); + + batadv_send_skb_packet(skb, hard_iface, dstaddr); + batadv_send_skb_packet(frag_skb, hard_iface, dstaddr); + ret = NET_RX_SUCCESS; + goto out; + +drop_frag: + kfree_skb(frag_skb); +dropped: + kfree_skb(skb); +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return ret; +} + +/** + * batadv_unicast_push_and_fill_skb - extends the buffer and initializes the + * common fields for unicast packets + * @skb: packet + * @hdr_size: amount of bytes to push at the beginning of the skb + * @orig_node: the destination node + * + * Returns false if the buffer extension was not possible or true otherwise + */ +static bool batadv_unicast_push_and_fill_skb(struct sk_buff *skb, int hdr_size, + struct batadv_orig_node *orig_node) +{ + struct batadv_unicast_packet *unicast_packet; + uint8_t ttvn = (uint8_t)atomic_read(&orig_node->last_ttvn); + + if (batadv_skb_head_push(skb, hdr_size) < 0) + return false; + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + unicast_packet->header.version = BATADV_COMPAT_VERSION; + /* batman packet type: unicast */ + unicast_packet->header.packet_type = BATADV_UNICAST; + /* set unicast ttl */ + unicast_packet->header.ttl = BATADV_TTL; + /* copy the destination for faster routing */ + memcpy(unicast_packet->dest, orig_node->orig, ETH_ALEN); + /* set the destination tt version number */ + unicast_packet->ttvn = ttvn; + + return true; +} + +/** + * batadv_unicast_prepare_skb - encapsulate an skb with a unicast header + * @skb: the skb containing the payload to encapsulate + * @orig_node: the destination node + * + * Returns false if the payload could not be encapsulated or true otherwise + */ +static bool batadv_unicast_prepare_skb(struct sk_buff *skb, + struct batadv_orig_node *orig_node) +{ + size_t uni_size = sizeof(struct batadv_unicast_packet); + return batadv_unicast_push_and_fill_skb(skb, uni_size, orig_node); +} + +/** + * batadv_unicast_4addr_prepare_skb - encapsulate an skb with a unicast4addr + * header + * @bat_priv: the bat priv with all the soft interface information + * @skb: the skb containing the payload to encapsulate + * @orig_node: the destination node + * @packet_subtype: the batman 4addr packet subtype to use + * + * Returns false if the payload could not be encapsulated or true otherwise + */ +bool batadv_unicast_4addr_prepare_skb(struct batadv_priv *bat_priv, + struct sk_buff *skb, + struct batadv_orig_node *orig, + int packet_subtype) +{ + struct batadv_hard_iface *primary_if; + struct batadv_unicast_4addr_packet *unicast_4addr_packet; + bool ret = false; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + /* pull the header space and fill the unicast_packet substructure. + * We can do that because the first member of the unicast_4addr_packet + * is of type struct unicast_packet + */ + if (!batadv_unicast_push_and_fill_skb(skb, + sizeof(*unicast_4addr_packet), + orig)) + goto out; + + unicast_4addr_packet = (struct batadv_unicast_4addr_packet *)skb->data; + unicast_4addr_packet->u.header.packet_type = BATADV_UNICAST_4ADDR; + memcpy(unicast_4addr_packet->src, primary_if->net_dev->dev_addr, + ETH_ALEN); + unicast_4addr_packet->subtype = packet_subtype; + unicast_4addr_packet->reserved = 0; + + ret = true; +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return ret; +} + +/** + * batadv_unicast_generic_send_skb - send an skb as unicast + * @bat_priv: the bat priv with all the soft interface information + * @skb: payload to send + * @packet_type: the batman unicast packet type to use + * @packet_subtype: the batman packet subtype. It is ignored if packet_type is + * not BATADV_UNICAT_4ADDR + * + * Returns 1 in case of error or 0 otherwise + */ +int batadv_unicast_generic_send_skb(struct batadv_priv *bat_priv, + struct sk_buff *skb, int packet_type, + int packet_subtype) +{ + struct ethhdr *ethhdr = (struct ethhdr *)skb->data; + struct batadv_unicast_packet *unicast_packet; + struct batadv_orig_node *orig_node; + struct batadv_neigh_node *neigh_node; + int data_len = skb->len; + int ret = NET_RX_DROP; + unsigned int dev_mtu; + + /* get routing information */ + if (is_multicast_ether_addr(ethhdr->h_dest)) { + orig_node = batadv_gw_get_selected_orig(bat_priv); + if (orig_node) + goto find_router; + } + + /* check for tt host - increases orig_node refcount. + * returns NULL in case of AP isolation + */ + orig_node = batadv_transtable_search(bat_priv, ethhdr->h_source, + ethhdr->h_dest); + +find_router: + /* find_router(): + * - if orig_node is NULL it returns NULL + * - increases neigh_nodes refcount if found. + */ + neigh_node = batadv_find_router(bat_priv, orig_node, NULL); + + if (!neigh_node) + goto out; + + switch (packet_type) { + case BATADV_UNICAST: + batadv_unicast_prepare_skb(skb, orig_node); + break; + case BATADV_UNICAST_4ADDR: + batadv_unicast_4addr_prepare_skb(bat_priv, skb, orig_node, + packet_subtype); + break; + default: + /* this function supports UNICAST and UNICAST_4ADDR only. It + * should never be invoked with any other packet type + */ + goto out; + } + + unicast_packet = (struct batadv_unicast_packet *)skb->data; + + /* inform the destination node that we are still missing a correct route + * for this client. The destination will receive this packet and will + * try to reroute it because the ttvn contained in the header is less + * than the current one + */ + if (batadv_tt_global_client_is_roaming(bat_priv, ethhdr->h_dest)) + unicast_packet->ttvn = unicast_packet->ttvn - 1; + + dev_mtu = neigh_node->if_incoming->net_dev->mtu; + /* fragmentation mechanism only works for UNICAST (now) */ + if (packet_type == BATADV_UNICAST && + atomic_read(&bat_priv->fragmentation) && + data_len + sizeof(*unicast_packet) > dev_mtu) { + /* send frag skb decreases ttl */ + unicast_packet->header.ttl++; + ret = batadv_frag_send_skb(skb, bat_priv, + neigh_node->if_incoming, + neigh_node->addr); + goto out; + } + + if (batadv_send_skb_to_orig(skb, orig_node, NULL)) + ret = 0; + +out: + if (neigh_node) + batadv_neigh_node_free_ref(neigh_node); + if (orig_node) + batadv_orig_node_free_ref(orig_node); + if (ret == NET_RX_DROP) + kfree_skb(skb); + return ret; +} diff --git a/net/batman-adv/unicast.h b/net/batman-adv/unicast.h new file mode 100644 index 00000000..429cf8a4 --- /dev/null +++ b/net/batman-adv/unicast.h @@ -0,0 +1,92 @@ +/* Copyright (C) 2010-2013 B.A.T.M.A.N. contributors: + * + * Andreas Langer + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_UNICAST_H_ +#define _NET_BATMAN_ADV_UNICAST_H_ + +#include "packet.h" + +#define BATADV_FRAG_TIMEOUT 10000 /* purge frag list entries after time in ms */ +#define BATADV_FRAG_BUFFER_SIZE 6 /* number of list elements in buffer */ + +int batadv_frag_reassemble_skb(struct sk_buff *skb, + struct batadv_priv *bat_priv, + struct sk_buff **new_skb); +void batadv_frag_list_free(struct list_head *head); +int batadv_frag_send_skb(struct sk_buff *skb, struct batadv_priv *bat_priv, + struct batadv_hard_iface *hard_iface, + const uint8_t dstaddr[]); +bool batadv_unicast_4addr_prepare_skb(struct batadv_priv *bat_priv, + struct sk_buff *skb, + struct batadv_orig_node *orig_node, + int packet_subtype); +int batadv_unicast_generic_send_skb(struct batadv_priv *bat_priv, + struct sk_buff *skb, int packet_type, + int packet_subtype); + + +/** + * batadv_unicast_send_skb - send the skb encapsulated in a unicast packet + * @bat_priv: the bat priv with all the soft interface information + * @skb: the payload to send + */ +static inline int batadv_unicast_send_skb(struct batadv_priv *bat_priv, + struct sk_buff *skb) +{ + return batadv_unicast_generic_send_skb(bat_priv, skb, BATADV_UNICAST, + 0); +} + +/** + * batadv_unicast_send_skb - send the skb encapsulated in a unicast4addr packet + * @bat_priv: the bat priv with all the soft interface information + * @skb: the payload to send + * @packet_subtype: the batman 4addr packet subtype to use + */ +static inline int batadv_unicast_4addr_send_skb(struct batadv_priv *bat_priv, + struct sk_buff *skb, + int packet_subtype) +{ + return batadv_unicast_generic_send_skb(bat_priv, skb, + BATADV_UNICAST_4ADDR, + packet_subtype); +} + +static inline int batadv_frag_can_reassemble(const struct sk_buff *skb, int mtu) +{ + const struct batadv_unicast_frag_packet *unicast_packet; + int uneven_correction = 0; + unsigned int merged_size; + + unicast_packet = (struct batadv_unicast_frag_packet *)skb->data; + + if (unicast_packet->flags & BATADV_UNI_FRAG_LARGETAIL) { + if (unicast_packet->flags & BATADV_UNI_FRAG_HEAD) + uneven_correction = 1; + else + uneven_correction = -1; + } + + merged_size = (skb->len - sizeof(*unicast_packet)) * 2; + merged_size += sizeof(struct batadv_unicast_packet) + uneven_correction; + + return merged_size <= mtu; +} + +#endif /* _NET_BATMAN_ADV_UNICAST_H_ */ diff --git a/net/batman-adv/vis.c b/net/batman-adv/vis.c new file mode 100644 index 00000000..1625e579 --- /dev/null +++ b/net/batman-adv/vis.c @@ -0,0 +1,935 @@ +/* Copyright (C) 2008-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich + * + * 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 "send.h" +#include "translation-table.h" +#include "vis.h" +#include "soft-interface.h" +#include "hard-interface.h" +#include "hash.h" +#include "originator.h" + +#define BATADV_MAX_VIS_PACKET_SIZE 1000 + +/* hash class keys */ +static struct lock_class_key batadv_vis_hash_lock_class_key; + +/* free the info */ +static void batadv_free_info(struct kref *ref) +{ + struct batadv_vis_info *info; + struct batadv_priv *bat_priv; + struct batadv_vis_recvlist_node *entry, *tmp; + + info = container_of(ref, struct batadv_vis_info, refcount); + bat_priv = info->bat_priv; + + list_del_init(&info->send_list); + spin_lock_bh(&bat_priv->vis.list_lock); + list_for_each_entry_safe(entry, tmp, &info->recv_list, list) { + list_del(&entry->list); + kfree(entry); + } + + spin_unlock_bh(&bat_priv->vis.list_lock); + kfree_skb(info->skb_packet); + kfree(info); +} + +/* Compare two vis packets, used by the hashing algorithm */ +static int batadv_vis_info_cmp(const struct hlist_node *node, const void *data2) +{ + const struct batadv_vis_info *d1, *d2; + const struct batadv_vis_packet *p1, *p2; + + d1 = container_of(node, struct batadv_vis_info, hash_entry); + d2 = data2; + p1 = (struct batadv_vis_packet *)d1->skb_packet->data; + p2 = (struct batadv_vis_packet *)d2->skb_packet->data; + return batadv_compare_eth(p1->vis_orig, p2->vis_orig); +} + +/* hash function to choose an entry in a hash table of given size + * hash algorithm from http://en.wikipedia.org/wiki/Hash_table + */ +static uint32_t batadv_vis_info_choose(const void *data, uint32_t size) +{ + const struct batadv_vis_info *vis_info = data; + const struct batadv_vis_packet *packet; + const unsigned char *key; + uint32_t hash = 0; + size_t i; + + packet = (struct batadv_vis_packet *)vis_info->skb_packet->data; + key = packet->vis_orig; + for (i = 0; i < ETH_ALEN; i++) { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + return hash % size; +} + +static struct batadv_vis_info * +batadv_vis_hash_find(struct batadv_priv *bat_priv, const void *data) +{ + struct batadv_hashtable *hash = bat_priv->vis.hash; + struct hlist_head *head; + struct batadv_vis_info *vis_info, *vis_info_tmp = NULL; + uint32_t index; + + if (!hash) + return NULL; + + index = batadv_vis_info_choose(data, hash->size); + head = &hash->table[index]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(vis_info, head, hash_entry) { + if (!batadv_vis_info_cmp(&vis_info->hash_entry, data)) + continue; + + vis_info_tmp = vis_info; + break; + } + rcu_read_unlock(); + + return vis_info_tmp; +} + +/* insert interface to the list of interfaces of one originator, if it + * does not already exist in the list + */ +static void batadv_vis_data_insert_interface(const uint8_t *interface, + struct hlist_head *if_list, + bool primary) +{ + struct batadv_vis_if_list_entry *entry; + + hlist_for_each_entry(entry, if_list, list) { + if (batadv_compare_eth(entry->addr, interface)) + return; + } + + /* it's a new address, add it to the list */ + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) + return; + memcpy(entry->addr, interface, ETH_ALEN); + entry->primary = primary; + hlist_add_head(&entry->list, if_list); +} + +static void batadv_vis_data_read_prim_sec(struct seq_file *seq, + const struct hlist_head *if_list) +{ + struct batadv_vis_if_list_entry *entry; + + hlist_for_each_entry(entry, if_list, list) { + if (entry->primary) + seq_puts(seq, "PRIMARY, "); + else + seq_printf(seq, "SEC %pM, ", entry->addr); + } +} + +/* read an entry */ +static ssize_t +batadv_vis_data_read_entry(struct seq_file *seq, + const struct batadv_vis_info_entry *entry, + const uint8_t *src, bool primary) +{ + if (primary && entry->quality == 0) + return seq_printf(seq, "TT %pM, ", entry->dest); + else if (batadv_compare_eth(entry->src, src)) + return seq_printf(seq, "TQ %pM %d, ", entry->dest, + entry->quality); + + return 0; +} + +static void +batadv_vis_data_insert_interfaces(struct hlist_head *list, + struct batadv_vis_packet *packet, + struct batadv_vis_info_entry *entries) +{ + int i; + + for (i = 0; i < packet->entries; i++) { + if (entries[i].quality == 0) + continue; + + if (batadv_compare_eth(entries[i].src, packet->vis_orig)) + continue; + + batadv_vis_data_insert_interface(entries[i].src, list, false); + } +} + +static void batadv_vis_data_read_entries(struct seq_file *seq, + struct hlist_head *list, + struct batadv_vis_packet *packet, + struct batadv_vis_info_entry *entries) +{ + int i; + struct batadv_vis_if_list_entry *entry; + + hlist_for_each_entry(entry, list, list) { + seq_printf(seq, "%pM,", entry->addr); + + for (i = 0; i < packet->entries; i++) + batadv_vis_data_read_entry(seq, &entries[i], + entry->addr, entry->primary); + + /* add primary/secondary records */ + if (batadv_compare_eth(entry->addr, packet->vis_orig)) + batadv_vis_data_read_prim_sec(seq, list); + + seq_puts(seq, "\n"); + } +} + +static void batadv_vis_seq_print_text_bucket(struct seq_file *seq, + const struct hlist_head *head) +{ + struct batadv_vis_info *info; + struct batadv_vis_packet *packet; + uint8_t *entries_pos; + struct batadv_vis_info_entry *entries; + struct batadv_vis_if_list_entry *entry; + struct hlist_node *n; + + HLIST_HEAD(vis_if_list); + + hlist_for_each_entry_rcu(info, head, hash_entry) { + packet = (struct batadv_vis_packet *)info->skb_packet->data; + entries_pos = (uint8_t *)packet + sizeof(*packet); + entries = (struct batadv_vis_info_entry *)entries_pos; + + batadv_vis_data_insert_interface(packet->vis_orig, &vis_if_list, + true); + batadv_vis_data_insert_interfaces(&vis_if_list, packet, + entries); + batadv_vis_data_read_entries(seq, &vis_if_list, packet, + entries); + + hlist_for_each_entry_safe(entry, n, &vis_if_list, list) { + hlist_del(&entry->list); + kfree(entry); + } + } +} + +int batadv_vis_seq_print_text(struct seq_file *seq, void *offset) +{ + struct batadv_hard_iface *primary_if; + struct hlist_head *head; + 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->vis.hash; + uint32_t i; + int ret = 0; + int vis_server = atomic_read(&bat_priv->vis_mode); + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + if (vis_server == BATADV_VIS_TYPE_CLIENT_UPDATE) + goto out; + + spin_lock_bh(&bat_priv->vis.hash_lock); + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + batadv_vis_seq_print_text_bucket(seq, head); + } + spin_unlock_bh(&bat_priv->vis.hash_lock); + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); + return ret; +} + +/* add the info packet to the send list, if it was not + * already linked in. + */ +static void batadv_send_list_add(struct batadv_priv *bat_priv, + struct batadv_vis_info *info) +{ + if (list_empty(&info->send_list)) { + kref_get(&info->refcount); + list_add_tail(&info->send_list, &bat_priv->vis.send_list); + } +} + +/* delete the info packet from the send list, if it was + * linked in. + */ +static void batadv_send_list_del(struct batadv_vis_info *info) +{ + if (!list_empty(&info->send_list)) { + list_del_init(&info->send_list); + kref_put(&info->refcount, batadv_free_info); + } +} + +/* tries to add one entry to the receive list. */ +static void batadv_recv_list_add(struct batadv_priv *bat_priv, + struct list_head *recv_list, const char *mac) +{ + struct batadv_vis_recvlist_node *entry; + + entry = kmalloc(sizeof(*entry), GFP_ATOMIC); + if (!entry) + return; + + memcpy(entry->mac, mac, ETH_ALEN); + spin_lock_bh(&bat_priv->vis.list_lock); + list_add_tail(&entry->list, recv_list); + spin_unlock_bh(&bat_priv->vis.list_lock); +} + +/* returns 1 if this mac is in the recv_list */ +static int batadv_recv_list_is_in(struct batadv_priv *bat_priv, + const struct list_head *recv_list, + const char *mac) +{ + const struct batadv_vis_recvlist_node *entry; + + spin_lock_bh(&bat_priv->vis.list_lock); + list_for_each_entry(entry, recv_list, list) { + if (batadv_compare_eth(entry->mac, mac)) { + spin_unlock_bh(&bat_priv->vis.list_lock); + return 1; + } + } + spin_unlock_bh(&bat_priv->vis.list_lock); + return 0; +} + +/* try to add the packet to the vis_hash. return NULL if invalid (e.g. too old, + * broken.. ). vis hash must be locked outside. is_new is set when the packet + * is newer than old entries in the hash. + */ +static struct batadv_vis_info * +batadv_add_packet(struct batadv_priv *bat_priv, + struct batadv_vis_packet *vis_packet, int vis_info_len, + int *is_new, int make_broadcast) +{ + struct batadv_vis_info *info, *old_info; + struct batadv_vis_packet *search_packet, *old_packet; + struct batadv_vis_info search_elem; + struct batadv_vis_packet *packet; + struct sk_buff *tmp_skb; + int hash_added; + size_t len; + size_t max_entries; + + *is_new = 0; + /* sanity check */ + if (!bat_priv->vis.hash) + return NULL; + + /* see if the packet is already in vis_hash */ + search_elem.skb_packet = dev_alloc_skb(sizeof(*search_packet)); + if (!search_elem.skb_packet) + return NULL; + len = sizeof(*search_packet); + tmp_skb = search_elem.skb_packet; + search_packet = (struct batadv_vis_packet *)skb_put(tmp_skb, len); + + memcpy(search_packet->vis_orig, vis_packet->vis_orig, ETH_ALEN); + old_info = batadv_vis_hash_find(bat_priv, &search_elem); + kfree_skb(search_elem.skb_packet); + + if (old_info) { + tmp_skb = old_info->skb_packet; + old_packet = (struct batadv_vis_packet *)tmp_skb->data; + if (!batadv_seq_after(ntohl(vis_packet->seqno), + ntohl(old_packet->seqno))) { + if (old_packet->seqno == vis_packet->seqno) { + batadv_recv_list_add(bat_priv, + &old_info->recv_list, + vis_packet->sender_orig); + return old_info; + } else { + /* newer packet is already in hash. */ + return NULL; + } + } + /* remove old entry */ + batadv_hash_remove(bat_priv->vis.hash, batadv_vis_info_cmp, + batadv_vis_info_choose, old_info); + batadv_send_list_del(old_info); + kref_put(&old_info->refcount, batadv_free_info); + } + + info = kmalloc(sizeof(*info), GFP_ATOMIC); + if (!info) + return NULL; + + len = sizeof(*packet) + vis_info_len; + info->skb_packet = dev_alloc_skb(len + ETH_HLEN + NET_IP_ALIGN); + if (!info->skb_packet) { + kfree(info); + return NULL; + } + skb_reserve(info->skb_packet, ETH_HLEN + NET_IP_ALIGN); + packet = (struct batadv_vis_packet *)skb_put(info->skb_packet, len); + + kref_init(&info->refcount); + INIT_LIST_HEAD(&info->send_list); + INIT_LIST_HEAD(&info->recv_list); + info->first_seen = jiffies; + info->bat_priv = bat_priv; + memcpy(packet, vis_packet, len); + + /* initialize and add new packet. */ + *is_new = 1; + + /* Make it a broadcast packet, if required */ + if (make_broadcast) + memcpy(packet->target_orig, batadv_broadcast_addr, ETH_ALEN); + + /* repair if entries is longer than packet. */ + max_entries = vis_info_len / sizeof(struct batadv_vis_info_entry); + if (packet->entries > max_entries) + packet->entries = max_entries; + + batadv_recv_list_add(bat_priv, &info->recv_list, packet->sender_orig); + + /* try to add it */ + hash_added = batadv_hash_add(bat_priv->vis.hash, batadv_vis_info_cmp, + batadv_vis_info_choose, info, + &info->hash_entry); + if (hash_added != 0) { + /* did not work (for some reason) */ + kref_put(&info->refcount, batadv_free_info); + info = NULL; + } + + return info; +} + +/* handle the server sync packet, forward if needed. */ +void batadv_receive_server_sync_packet(struct batadv_priv *bat_priv, + struct batadv_vis_packet *vis_packet, + int vis_info_len) +{ + struct batadv_vis_info *info; + int is_new, make_broadcast; + int vis_server = atomic_read(&bat_priv->vis_mode); + + make_broadcast = (vis_server == BATADV_VIS_TYPE_SERVER_SYNC); + + spin_lock_bh(&bat_priv->vis.hash_lock); + info = batadv_add_packet(bat_priv, vis_packet, vis_info_len, + &is_new, make_broadcast); + if (!info) + goto end; + + /* only if we are server ourselves and packet is newer than the one in + * hash. + */ + if (vis_server == BATADV_VIS_TYPE_SERVER_SYNC && is_new) + batadv_send_list_add(bat_priv, info); +end: + spin_unlock_bh(&bat_priv->vis.hash_lock); +} + +/* handle an incoming client update packet and schedule forward if needed. */ +void batadv_receive_client_update_packet(struct batadv_priv *bat_priv, + struct batadv_vis_packet *vis_packet, + int vis_info_len) +{ + struct batadv_vis_info *info; + struct batadv_vis_packet *packet; + int is_new; + int vis_server = atomic_read(&bat_priv->vis_mode); + int are_target = 0; + + /* clients shall not broadcast. */ + if (is_broadcast_ether_addr(vis_packet->target_orig)) + return; + + /* Are we the target for this VIS packet? */ + if (vis_server == BATADV_VIS_TYPE_SERVER_SYNC && + batadv_is_my_mac(bat_priv, vis_packet->target_orig)) + are_target = 1; + + spin_lock_bh(&bat_priv->vis.hash_lock); + info = batadv_add_packet(bat_priv, vis_packet, vis_info_len, + &is_new, are_target); + + if (!info) + goto end; + /* note that outdated packets will be dropped at this point. */ + + packet = (struct batadv_vis_packet *)info->skb_packet->data; + + /* send only if we're the target server or ... */ + if (are_target && is_new) { + packet->vis_type = BATADV_VIS_TYPE_SERVER_SYNC; /* upgrade! */ + batadv_send_list_add(bat_priv, info); + + /* ... we're not the recipient (and thus need to forward). */ + } else if (!batadv_is_my_mac(bat_priv, packet->target_orig)) { + batadv_send_list_add(bat_priv, info); + } + +end: + spin_unlock_bh(&bat_priv->vis.hash_lock); +} + +/* Walk the originators and find the VIS server with the best tq. Set the packet + * address to its address and return the best_tq. + * + * Must be called with the originator hash locked + */ +static int batadv_find_best_vis_server(struct batadv_priv *bat_priv, + struct batadv_vis_info *info) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct batadv_neigh_node *router; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + struct batadv_vis_packet *packet; + int best_tq = -1; + uint32_t i; + + packet = (struct batadv_vis_packet *)info->skb_packet->data; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + router = batadv_orig_node_get_router(orig_node); + if (!router) + continue; + + if ((orig_node->flags & BATADV_VIS_SERVER) && + (router->tq_avg > best_tq)) { + best_tq = router->tq_avg; + memcpy(packet->target_orig, orig_node->orig, + ETH_ALEN); + } + batadv_neigh_node_free_ref(router); + } + rcu_read_unlock(); + } + + return best_tq; +} + +/* Return true if the vis packet is full. */ +static bool batadv_vis_packet_full(const struct batadv_vis_info *info) +{ + const struct batadv_vis_packet *packet; + size_t num; + + packet = (struct batadv_vis_packet *)info->skb_packet->data; + num = BATADV_MAX_VIS_PACKET_SIZE / sizeof(struct batadv_vis_info_entry); + + if (num < packet->entries + 1) + return true; + return false; +} + +/* generates a packet of own vis data, + * returns 0 on success, -1 if no packet could be generated + */ +static int batadv_generate_vis_packet(struct batadv_priv *bat_priv) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + struct batadv_neigh_node *router; + struct batadv_vis_info *info = bat_priv->vis.my_info; + struct batadv_vis_packet *packet; + struct batadv_vis_info_entry *entry; + struct batadv_tt_common_entry *tt_common_entry; + uint8_t *packet_pos; + int best_tq = -1; + uint32_t i; + + info->first_seen = jiffies; + packet = (struct batadv_vis_packet *)info->skb_packet->data; + packet->vis_type = atomic_read(&bat_priv->vis_mode); + + memcpy(packet->target_orig, batadv_broadcast_addr, ETH_ALEN); + packet->header.ttl = BATADV_TTL; + packet->seqno = htonl(ntohl(packet->seqno) + 1); + packet->entries = 0; + packet->reserved = 0; + skb_trim(info->skb_packet, sizeof(*packet)); + + if (packet->vis_type == BATADV_VIS_TYPE_CLIENT_UPDATE) { + best_tq = batadv_find_best_vis_server(bat_priv, info); + + if (best_tq < 0) + return best_tq; + } + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + router = batadv_orig_node_get_router(orig_node); + if (!router) + continue; + + if (!batadv_compare_eth(router->addr, orig_node->orig)) + goto next; + + if (router->if_incoming->if_status != BATADV_IF_ACTIVE) + goto next; + + if (router->tq_avg < 1) + goto next; + + /* fill one entry into buffer. */ + packet_pos = skb_put(info->skb_packet, sizeof(*entry)); + entry = (struct batadv_vis_info_entry *)packet_pos; + memcpy(entry->src, + router->if_incoming->net_dev->dev_addr, + ETH_ALEN); + memcpy(entry->dest, orig_node->orig, ETH_ALEN); + entry->quality = router->tq_avg; + packet->entries++; + +next: + batadv_neigh_node_free_ref(router); + + if (batadv_vis_packet_full(info)) + goto unlock; + } + rcu_read_unlock(); + } + + hash = bat_priv->tt.local_hash; + + 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) { + packet_pos = skb_put(info->skb_packet, sizeof(*entry)); + entry = (struct batadv_vis_info_entry *)packet_pos; + memset(entry->src, 0, ETH_ALEN); + memcpy(entry->dest, tt_common_entry->addr, ETH_ALEN); + entry->quality = 0; /* 0 means TT */ + packet->entries++; + + if (batadv_vis_packet_full(info)) + goto unlock; + } + rcu_read_unlock(); + } + + return 0; + +unlock: + rcu_read_unlock(); + return 0; +} + +/* free old vis packets. Must be called with this vis_hash_lock + * held + */ +static void batadv_purge_vis_packets(struct batadv_priv *bat_priv) +{ + uint32_t i; + struct batadv_hashtable *hash = bat_priv->vis.hash; + struct hlist_node *node_tmp; + struct hlist_head *head; + struct batadv_vis_info *info; + + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + hlist_for_each_entry_safe(info, node_tmp, + head, hash_entry) { + /* never purge own data. */ + if (info == bat_priv->vis.my_info) + continue; + + if (batadv_has_timed_out(info->first_seen, + BATADV_VIS_TIMEOUT)) { + hlist_del(&info->hash_entry); + batadv_send_list_del(info); + kref_put(&info->refcount, batadv_free_info); + } + } + } +} + +static void batadv_broadcast_vis_packet(struct batadv_priv *bat_priv, + struct batadv_vis_info *info) +{ + struct batadv_hashtable *hash = bat_priv->orig_hash; + struct hlist_head *head; + struct batadv_orig_node *orig_node; + struct batadv_vis_packet *packet; + struct sk_buff *skb; + uint32_t i; + + + packet = (struct batadv_vis_packet *)info->skb_packet->data; + + /* send to all routers in range. */ + for (i = 0; i < hash->size; i++) { + head = &hash->table[i]; + + rcu_read_lock(); + hlist_for_each_entry_rcu(orig_node, head, hash_entry) { + /* if it's a vis server and reachable, send it. */ + if (!(orig_node->flags & BATADV_VIS_SERVER)) + continue; + + /* don't send it if we already received the packet from + * this node. + */ + if (batadv_recv_list_is_in(bat_priv, &info->recv_list, + orig_node->orig)) + continue; + + memcpy(packet->target_orig, orig_node->orig, ETH_ALEN); + skb = skb_clone(info->skb_packet, GFP_ATOMIC); + if (!skb) + continue; + + if (!batadv_send_skb_to_orig(skb, orig_node, NULL)) + kfree_skb(skb); + } + rcu_read_unlock(); + } +} + +static void batadv_unicast_vis_packet(struct batadv_priv *bat_priv, + struct batadv_vis_info *info) +{ + struct batadv_orig_node *orig_node; + struct sk_buff *skb; + struct batadv_vis_packet *packet; + + packet = (struct batadv_vis_packet *)info->skb_packet->data; + + orig_node = batadv_orig_hash_find(bat_priv, packet->target_orig); + if (!orig_node) + goto out; + + skb = skb_clone(info->skb_packet, GFP_ATOMIC); + if (!skb) + goto out; + + if (!batadv_send_skb_to_orig(skb, orig_node, NULL)) + kfree_skb(skb); + +out: + if (orig_node) + batadv_orig_node_free_ref(orig_node); +} + +/* only send one vis packet. called from batadv_send_vis_packets() */ +static void batadv_send_vis_packet(struct batadv_priv *bat_priv, + struct batadv_vis_info *info) +{ + struct batadv_hard_iface *primary_if; + struct batadv_vis_packet *packet; + + primary_if = batadv_primary_if_get_selected(bat_priv); + if (!primary_if) + goto out; + + packet = (struct batadv_vis_packet *)info->skb_packet->data; + if (packet->header.ttl < 2) { + pr_debug("Error - can't send vis packet: ttl exceeded\n"); + goto out; + } + + memcpy(packet->sender_orig, primary_if->net_dev->dev_addr, ETH_ALEN); + packet->header.ttl--; + + if (is_broadcast_ether_addr(packet->target_orig)) + batadv_broadcast_vis_packet(bat_priv, info); + else + batadv_unicast_vis_packet(bat_priv, info); + packet->header.ttl++; /* restore TTL */ + +out: + if (primary_if) + batadv_hardif_free_ref(primary_if); +} + +/* called from timer; send (and maybe generate) vis packet. */ +static void batadv_send_vis_packets(struct work_struct *work) +{ + struct delayed_work *delayed_work; + struct batadv_priv *bat_priv; + struct batadv_priv_vis *priv_vis; + struct batadv_vis_info *info; + + delayed_work = container_of(work, struct delayed_work, work); + priv_vis = container_of(delayed_work, struct batadv_priv_vis, work); + bat_priv = container_of(priv_vis, struct batadv_priv, vis); + spin_lock_bh(&bat_priv->vis.hash_lock); + batadv_purge_vis_packets(bat_priv); + + if (batadv_generate_vis_packet(bat_priv) == 0) { + /* schedule if generation was successful */ + batadv_send_list_add(bat_priv, bat_priv->vis.my_info); + } + + while (!list_empty(&bat_priv->vis.send_list)) { + info = list_first_entry(&bat_priv->vis.send_list, + typeof(*info), send_list); + + kref_get(&info->refcount); + spin_unlock_bh(&bat_priv->vis.hash_lock); + + batadv_send_vis_packet(bat_priv, info); + + spin_lock_bh(&bat_priv->vis.hash_lock); + batadv_send_list_del(info); + kref_put(&info->refcount, batadv_free_info); + } + spin_unlock_bh(&bat_priv->vis.hash_lock); + + queue_delayed_work(batadv_event_workqueue, &bat_priv->vis.work, + msecs_to_jiffies(BATADV_VIS_INTERVAL)); +} + +/* init the vis server. this may only be called when if_list is already + * initialized (e.g. bat0 is initialized, interfaces have been added) + */ +int batadv_vis_init(struct batadv_priv *bat_priv) +{ + struct batadv_vis_packet *packet; + int hash_added; + unsigned int len; + unsigned long first_seen; + struct sk_buff *tmp_skb; + + if (bat_priv->vis.hash) + return 0; + + spin_lock_bh(&bat_priv->vis.hash_lock); + + bat_priv->vis.hash = batadv_hash_new(256); + if (!bat_priv->vis.hash) { + pr_err("Can't initialize vis_hash\n"); + goto err; + } + + batadv_hash_set_lock_class(bat_priv->vis.hash, + &batadv_vis_hash_lock_class_key); + + bat_priv->vis.my_info = kmalloc(BATADV_MAX_VIS_PACKET_SIZE, GFP_ATOMIC); + if (!bat_priv->vis.my_info) + goto err; + + len = sizeof(*packet) + BATADV_MAX_VIS_PACKET_SIZE; + len += ETH_HLEN + NET_IP_ALIGN; + bat_priv->vis.my_info->skb_packet = dev_alloc_skb(len); + if (!bat_priv->vis.my_info->skb_packet) + goto free_info; + + skb_reserve(bat_priv->vis.my_info->skb_packet, ETH_HLEN + NET_IP_ALIGN); + tmp_skb = bat_priv->vis.my_info->skb_packet; + packet = (struct batadv_vis_packet *)skb_put(tmp_skb, sizeof(*packet)); + + /* prefill the vis info */ + first_seen = jiffies - msecs_to_jiffies(BATADV_VIS_INTERVAL); + bat_priv->vis.my_info->first_seen = first_seen; + INIT_LIST_HEAD(&bat_priv->vis.my_info->recv_list); + INIT_LIST_HEAD(&bat_priv->vis.my_info->send_list); + kref_init(&bat_priv->vis.my_info->refcount); + bat_priv->vis.my_info->bat_priv = bat_priv; + packet->header.version = BATADV_COMPAT_VERSION; + packet->header.packet_type = BATADV_VIS; + packet->header.ttl = BATADV_TTL; + packet->seqno = 0; + packet->reserved = 0; + packet->entries = 0; + + INIT_LIST_HEAD(&bat_priv->vis.send_list); + + hash_added = batadv_hash_add(bat_priv->vis.hash, batadv_vis_info_cmp, + batadv_vis_info_choose, + bat_priv->vis.my_info, + &bat_priv->vis.my_info->hash_entry); + if (hash_added != 0) { + pr_err("Can't add own vis packet into hash\n"); + /* not in hash, need to remove it manually. */ + kref_put(&bat_priv->vis.my_info->refcount, batadv_free_info); + goto err; + } + + spin_unlock_bh(&bat_priv->vis.hash_lock); + + INIT_DELAYED_WORK(&bat_priv->vis.work, batadv_send_vis_packets); + queue_delayed_work(batadv_event_workqueue, &bat_priv->vis.work, + msecs_to_jiffies(BATADV_VIS_INTERVAL)); + + return 0; + +free_info: + kfree(bat_priv->vis.my_info); + bat_priv->vis.my_info = NULL; +err: + spin_unlock_bh(&bat_priv->vis.hash_lock); + batadv_vis_quit(bat_priv); + return -ENOMEM; +} + +/* Decrease the reference count on a hash item info */ +static void batadv_free_info_ref(struct hlist_node *node, void *arg) +{ + struct batadv_vis_info *info; + + info = container_of(node, struct batadv_vis_info, hash_entry); + batadv_send_list_del(info); + kref_put(&info->refcount, batadv_free_info); +} + +/* shutdown vis-server */ +void batadv_vis_quit(struct batadv_priv *bat_priv) +{ + if (!bat_priv->vis.hash) + return; + + cancel_delayed_work_sync(&bat_priv->vis.work); + + spin_lock_bh(&bat_priv->vis.hash_lock); + /* properly remove, kill timers ... */ + batadv_hash_delete(bat_priv->vis.hash, batadv_free_info_ref, NULL); + bat_priv->vis.hash = NULL; + bat_priv->vis.my_info = NULL; + spin_unlock_bh(&bat_priv->vis.hash_lock); +} diff --git a/net/batman-adv/vis.h b/net/batman-adv/vis.h new file mode 100644 index 00000000..ad92b0e3 --- /dev/null +++ b/net/batman-adv/vis.h @@ -0,0 +1,36 @@ +/* Copyright (C) 2008-2013 B.A.T.M.A.N. contributors: + * + * Simon Wunderlich, Marek Lindner + * + * 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 + */ + +#ifndef _NET_BATMAN_ADV_VIS_H_ +#define _NET_BATMAN_ADV_VIS_H_ + +/* timeout of vis packets in milliseconds */ +#define BATADV_VIS_TIMEOUT 200000 + +int batadv_vis_seq_print_text(struct seq_file *seq, void *offset); +void batadv_receive_server_sync_packet(struct batadv_priv *bat_priv, + struct batadv_vis_packet *vis_packet, + int vis_info_len); +void batadv_receive_client_update_packet(struct batadv_priv *bat_priv, + struct batadv_vis_packet *vis_packet, + int vis_info_len); +int batadv_vis_init(struct batadv_priv *bat_priv); +void batadv_vis_quit(struct batadv_priv *bat_priv); + +#endif /* _NET_BATMAN_ADV_VIS_H_ */ |
