The following commit has been merged in the merge/master branch: commit 1a95fced5523eb1f601d2df341259ba59c7074d0 Merge: 34d195c82ad86bccf772d26083155adbe5b6bd24 18491357187f9fb8f0e56eb4a9a8af8f793dfd08 Author: Antonio Quartulli ordex@autistici.org Date: Wed Mar 6 09:10:33 2013 +0100
Merge remote-tracking branch 'pkg/next' into merge/master
Signed-off-by: Antonio Quartulli ordex@autistici.org
Conflicts: net/batman-adv/Makefile net/batman-adv/Makefile.kbuild net/batman-adv/compat.c net/batman-adv/compat.h net/batman-adv/gen-compat-autoconf.sh net/batman-adv/sysfs-class-net-mesh
diff --combined Documentation/ABI/testing/sysfs-class-net-mesh index bc41da6,bdcd8b4..bdcd8b4 --- a/Documentation/ABI/testing/sysfs-class-net-mesh +++ b/Documentation/ABI/testing/sysfs-class-net-mesh @@@ -67,6 -67,14 +67,14 @@@ Description Defines the penalty which will be applied to an originator message's tq-field on every hop.
+ What: /sys/class/net/<mesh_iface>/mesh/network_coding + Date: Nov 2012 + Contact: Martin Hundeboll martin@hundeboll.net + Description: + Controls whether Network Coding (using some magic + to send fewer wifi packets but still the same + content) is enabled or not. + What: /sys/class/net/<mesh_iface>/mesh/orig_interval Date: May 2010 Contact: Marek Lindner lindner_marek@yahoo.de diff --combined net/batman-adv/Makefile index 9beebf0,acbac2a..acbac2a --- a/net/batman-adv/Makefile +++ b/net/batman-adv/Makefile @@@ -30,6 -30,7 +30,7 @@@ batman-adv-y += hard-interface. 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 diff --combined net/batman-adv/bat_iv_ogm.c index a0b253e,0000000..c8a0b65 mode 100644,000000..100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@@ -1,1339 -1,0 +1,1344 @@@ +/* 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" + +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, + int is_duplicate) +{ + 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 (is_duplicate) + 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 (!is_duplicate) { + 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; +} + +/* processes a batman packet for all interfaces, adjusts the sequence number and + * finds out whether it is a duplicate. + * returns: + * 1 the packet is a duplicate + * 0 the packet has not yet been received + * -1 the packet is old and has been received while the seqno window + * was protected. Caller should drop it. + */ +static int +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_duplicate = 0; + int32_t seq_diff; + int need_update = 0; + int set_mark, ret = -1; + 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 0; + + 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)) + goto out; + + rcu_read_lock(); + hlist_for_each_entry_rcu(tmp_neigh_node, + &orig_node->neigh_list, list) { + is_duplicate |= batadv_test_bit(tmp_neigh_node->real_bits, + orig_node->last_real_seqno, + seqno); + + neigh_addr = tmp_neigh_node->addr; + if (batadv_compare_eth(neigh_addr, ethhdr->h_source) && + tmp_neigh_node->if_incoming == if_incoming) + set_mark = 1; + else + set_mark = 0; + + /* 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; + } + + ret = is_duplicate; + +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 is_duplicate, sameseq, simlar_ttl; + 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; + + is_duplicate = batadv_iv_ogm_update_seqnos(ethhdr, batadv_ogm_packet, + if_incoming); + + if (is_duplicate == -1) { + 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); + simlar_ttl = orig_node->last_ttl - 3 <= batadv_ogm_packet->header.ttl; + if (is_bidirect && (!is_duplicate || (sameseq && simlar_ttl))) + batadv_iv_ogm_orig_update(bat_priv, orig_node, ethhdr, + batadv_ogm_packet, if_incoming, + tt_buff, is_duplicate); + + /* 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 (is_duplicate) { + 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 */ + do { + 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; + } while (batadv_iv_ogm_aggr_packet(buff_pos, packet_len, + batadv_ogm_packet->tt_num_changes)); + + 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 --combined net/batman-adv/debugfs.c index 6ae8651,f186a55..f186a55 --- a/net/batman-adv/debugfs.c +++ b/net/batman-adv/debugfs.c @@@ -32,6 -32,7 +32,7 @@@ #include "icmp_socket.h" #include "bridge_loop_avoidance.h" #include "distributed-arp-table.h" + #include "network-coding.h"
static struct dentry *batadv_debugfs;
@@@ -310,6 -311,14 +311,14 @@@ struct batadv_debuginfo 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), \ @@@ -348,6 -357,9 +357,9 @@@ static BATADV_DEBUGINFO(dat_cache, S_IR 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, @@@ -362,6 -374,9 +374,9 @@@ #endif &batadv_debuginfo_transtable_local, &batadv_debuginfo_vis_data, + #ifdef CONFIG_BATMAN_ADV_NC + &batadv_debuginfo_nc_nodes, + #endif NULL, };
@@@ -431,6 -446,9 +446,9 @@@ int batadv_debugfs_add_meshif(struct ne } }
+ if (batadv_nc_init_debugfs(bat_priv) < 0) + goto rem_attr; + return 0; rem_attr: debugfs_remove_recursive(bat_priv->debug_dir); diff --combined net/batman-adv/distributed-arp-table.c index d54188a,0000000..8e15d96 mode 100644,000000..100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@@ -1,1080 -1,0 +1,1066 @@@ +/* 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; - struct batadv_hard_iface *primary_if = NULL; + + 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) { - primary_if = batadv_primary_if_get_selected(bat_priv); - if (!primary_if) - goto out; - + skb_new = arp_create(ARPOP_REPLY, ETH_P_ARP, ip_src, - primary_if->soft_iface, ip_dst, hw_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, - primary_if->soft_iface); ++ bat_priv->soft_iface); + bat_priv->stats.rx_packets++; + bat_priv->stats.rx_bytes += skb->len + ETH_HLEN; - primary_if->soft_iface->last_rx = jiffies; ++ 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); - if (primary_if) - batadv_hardif_free_ref(primary_if); + 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_hard_iface *primary_if = NULL; + 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; + - primary_if = batadv_primary_if_get_selected(bat_priv); - if (!primary_if) - goto out; - + skb_new = arp_create(ARPOP_REPLY, ETH_P_ARP, ip_src, - primary_if->soft_iface, ip_dst, hw_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 (primary_if) - batadv_hardif_free_ref(primary_if); + 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 --combined net/batman-adv/hard-interface.c index 368219e,fd99e42..fd99e42 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@@ -311,7 -311,7 +311,7 @@@ int batadv_hardif_enable_interface(stru const char *iface_name) { struct batadv_priv *bat_priv; - struct net_device *soft_iface; + struct net_device *soft_iface, *master; __be16 ethertype = __constant_htons(ETH_P_BATMAN); int ret;
@@@ -321,11 -321,6 +321,6 @@@ if (!atomic_inc_not_zero(&hard_iface->refcount)) goto out;
- /* hard-interface is part of a bridge */ - if (hard_iface->net_dev->priv_flags & IFF_BRIDGE_PORT) - pr_err("You are about to enable batman-adv on '%s' which already is part of a bridge. Unless you know exactly what you are doing this is probably wrong and won't work the way you think it would.\n", - hard_iface->net_dev->name); - soft_iface = dev_get_by_name(&init_net, iface_name);
if (!soft_iface) { @@@ -347,12 -342,23 +342,23 @@@ 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); + if (master) + netdev_upper_dev_unlink(hard_iface->net_dev, master); + 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_dev; + goto err_upper;
hard_iface->if_num = bat_priv->num_ifaces; bat_priv->num_ifaces++; @@@ -362,7 -368,7 +368,7 @@@ 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_dev; + goto err_upper; }
hard_iface->batman_adv_ptype.type = ethertype; @@@ -401,6 -407,8 +407,8 @@@ out: return 0;
+ err_upper: + netdev_upper_dev_unlink(hard_iface->net_dev, soft_iface); err_dev: dev_put(soft_iface); err: @@@ -408,7 -416,8 +416,8 @@@ return ret; }
- void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface) + 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; @@@ -446,9 -455,10 +455,10 @@@ dev_put(hard_iface->soft_iface);
/* nobody uses this interface anymore */ - if (!bat_priv->num_ifaces) - batadv_softif_destroy(hard_iface->soft_iface); + 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);
@@@ -533,7 -543,8 +543,8 @@@ static void batadv_hardif_remove_interf
/* first deactivate interface */ if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) - batadv_hardif_disable_interface(hard_iface); + batadv_hardif_disable_interface(hard_iface, + BATADV_IF_CLEANUP_AUTO);
if (hard_iface->if_status != BATADV_IF_NOT_IN_USE) return; @@@ -563,6 -574,11 +574,11 @@@ static int batadv_hard_if_event(struct 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); diff --combined net/batman-adv/hard-interface.h index 308437d,4989288..4989288 --- a/net/batman-adv/hard-interface.h +++ b/net/batman-adv/hard-interface.h @@@ -29,13 -29,24 +29,24 @@@ enum batadv_hard_if_state 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); + 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); diff --combined net/batman-adv/main.c index 0488d70,0000000..62b1f89 mode 100644,000000..100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@@ -1,493 -1,0 +1,501 @@@ +/* 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_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_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_originator_free(bat_priv); ++ batadv_nc_free(bat_priv); + + batadv_tt_free(bat_priv); + + batadv_bla_free(bat_priv); + + batadv_dat_free(bat_priv); + + free_percpu(bat_priv->bat_counters); + + atomic_set(&bat_priv->mesh_state, BATADV_MESH_INACTIVE); +} + +int batadv_is_my_mac(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 (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_printf(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 (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 --combined net/batman-adv/main.h index ced08b9,0000000..f90f5bc mode 100644,000000..100644 --- a/net/batman-adv/main.h +++ b/net/batman-adv/main.h @@@ -1,301 -1,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.1.0" ++#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(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_ALL = 31, ++ 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 --combined net/batman-adv/network-coding.c index 0000000,e6b192c..e6b192c mode 000000,100644..100644 --- a/net/batman-adv/network-coding.c +++ b/net/batman-adv/network-coding.c @@@ -1,0 -1,1829 +1,1829 @@@ + /* 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_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_init - initialise coding hash table and start house keeping + * @bat_priv: the bat priv with all the soft interface information + */ + int batadv_nc_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); + + /* Register our packet type */ + if (batadv_recv_handler_register(BATADV_CODED, + batadv_nc_recv_coded_packet) < 0) + goto err; + + 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_node *node; + 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, 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, *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, 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(node); + 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 hlist_node *node; + 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, node, head, hash_entry) { + if (!batadv_nc_hash_compare(node, 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_node *node; + 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, node, 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 != 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 hlist_node *node; + 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, node, &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(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 + * @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 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; + + /* 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(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) + pskb_trim_rcsum(skb, coding_len + h_size); + + /* 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 hlist_node *hnode; + 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(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, hnode, &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(ethhdr->h_dest) && + !batadv_is_my_mac(coded_packet->second_dest)) + return NET_RX_DROP; + + /* Update stat counter */ + if (batadv_is_my_mac(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(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_free - clean up network coding memory + * @bat_priv: the bat priv with all the soft interface information + */ + void batadv_nc_free(struct batadv_priv *bat_priv) + { + batadv_recv_handler_unregister(BATADV_CODED); + 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_node *node; + 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, node, head, hash_entry) { + seq_printf(seq, "Node: %pM\n", orig_node->orig); + + seq_printf(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_printf(seq, "\n"); + + seq_printf(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_printf(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 --combined net/batman-adv/network-coding.h index 0000000,352c693..352c693 mode 000000,100644..100644 --- a/net/batman-adv/network-coding.h +++ b/net/batman-adv/network-coding.h @@@ -1,0 -1,123 +1,123 @@@ + /* 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(struct batadv_priv *bat_priv); + void batadv_nc_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(struct batadv_priv *bat_priv) + { + return 0; + } + + static inline void batadv_nc_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 --combined net/batman-adv/originator.c index 96fb80b,0000000..585e684 mode 100644,000000..100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@@ -1,644 -1,0 +1,650 @@@ +/* 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); +} + +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); +} + +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_printf(seq, "\n"); + batman_count++; + +next: + batadv_neigh_node_free_ref(neigh_node); + } + rcu_read_unlock(); + } + + if (batman_count == 0) + seq_printf(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 --combined net/batman-adv/packet.h index ed0aa89,a079958..a079958 --- a/net/batman-adv/packet.h +++ b/net/batman-adv/packet.h @@@ -30,6 -30,7 +30,7 @@@ enum batadv_packettype BATADV_TT_QUERY = 0x07, BATADV_ROAM_ADV = 0x08, BATADV_UNICAST_4ADDR = 0x09, + BATADV_CODED = 0x0a, };
/** @@@ -278,4 -279,36 +279,36 @@@ struct batadv_tt_change 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; + uint16_t coded_len; + }; + #endif /* _NET_BATMAN_ADV_PACKET_H_ */ diff --combined net/batman-adv/routing.c index 5ee21ce,0000000..8f88967 mode 100644,000000..100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@@ -1,1275 -1,0 +1,1298 @@@ +/* 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(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(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 ++ * @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 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 -1; ++ 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 -1; ++ return -EBADR; + + /* packet with broadcast sender address */ + if (is_broadcast_ether_addr(ethhdr->h_source)) - return -1; ++ return -EBADR; + + /* not for me */ + if (!batadv_is_my_mac(ethhdr->h_dest)) - return -1; ++ 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(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(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(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(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--; + - /* Update stats counter */ - batadv_inc_counter(bat_priv, BATADV_CNT_FORWARD); - batadv_add_counter(bat_priv, BATADV_CNT_FORWARD_BYTES, - skb->len + ETH_HLEN); - - /* route it */ - if (batadv_send_skb_to_orig(skb, orig_node, recv_if)) ++ /* 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) { + 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, sizeof(*unicast_packet) + 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 + sizeof(*unicast_packet)); + + /* 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(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 hdr_size = sizeof(*unicast_packet); ++ 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); + - if (batadv_check_unicast_packet(skb, hdr_size) < 0) ++ /* function returns -EREMOTE for promiscuous packets */ ++ check = batadv_check_unicast_packet(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)) + return NET_RX_DROP; + + /* packet for me */ + if (batadv_is_my_mac(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(skb, hdr_size) < 0) + return NET_RX_DROP; + + if (!batadv_check_unicast_ttvn(bat_priv, skb)) + return NET_RX_DROP; + + unicast_packet = (struct batadv_unicast_frag_packet *)skb->data; + + /* packet for me */ + if (batadv_is_my_mac(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(ethhdr->h_source)) + goto out; + + bcast_packet = (struct batadv_bcast_packet *)skb->data; + + /* ignore broadcasts originated by myself */ + if (batadv_is_my_mac(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(ethhdr->h_dest)) + return NET_RX_DROP; + + /* ignore own packets */ + if (batadv_is_my_mac(vis_packet->vis_orig)) + return NET_RX_DROP; + + if (batadv_is_my_mac(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 --combined net/batman-adv/send.c index a67cffd,0000000..263cfd1 mode 100644,000000..100644 --- a/net/batman-adv/send.c +++ b/net/batman-adv/send.c @@@ -1,381 -1,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 --combined net/batman-adv/soft-interface.c index 2711e87,403b8c4..403b8c4 --- a/net/batman-adv/soft-interface.c +++ b/net/batman-adv/soft-interface.c @@@ -37,6 -37,7 +37,7 @@@ #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); @@@ -401,55 -402,6 +402,6 @@@ static void batadv_set_lockdep_class(st }
/** - * batadv_softif_init - Late stage initialization of soft interface - * @dev: registered network device to modify - * - * Returns error code on failures - */ - static int batadv_softif_init(struct net_device *dev) - { - batadv_set_lockdep_class(dev); - - return 0; - } - - static const struct net_device_ops batadv_netdev_ops = { - .ndo_init = batadv_softif_init, - .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 - }; - - static void batadv_interface_setup(struct net_device *dev) - { - struct batadv_priv *priv = netdev_priv(dev); - - ether_setup(dev); - - dev->netdev_ops = &batadv_netdev_ops; - dev->destructor = free_netdev; - 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)); - } - - /** * batadv_softif_destroy_finish - cleans up the remains of a softif * @work: work queue item * @@@ -465,7 -417,6 +417,6 @@@ static void batadv_softif_destroy_finis cleanup_work); soft_iface = bat_priv->soft_iface;
- batadv_debugfs_del_meshif(soft_iface); batadv_sysfs_del_meshif(soft_iface);
rtnl_lock(); @@@ -473,21 -424,22 +424,22 @@@ rtnl_unlock(); }
- struct net_device *batadv_softif_create(const char *name) + /** + * 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 net_device *soft_iface; struct batadv_priv *bat_priv; int ret; size_t cnt_len = sizeof(uint64_t) * BATADV_CNT_NUM;
- soft_iface = alloc_netdev(sizeof(*bat_priv), name, - batadv_interface_setup); - - if (!soft_iface) - goto out; + batadv_set_lockdep_class(dev);
- bat_priv = netdev_priv(soft_iface); - bat_priv->soft_iface = soft_iface; + 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 @@@ -495,14 -447,7 +447,7 @@@ */ bat_priv->bat_counters = __alloc_percpu(cnt_len, __alignof__(uint64_t)); if (!bat_priv->bat_counters) - goto free_soft_iface; - - ret = register_netdevice(soft_iface); - if (ret < 0) { - pr_err("Unable to register the batman interface '%s': %i\n", - name, ret); - goto free_bat_counters; - } + return -ENOMEM;
atomic_set(&bat_priv->aggregated_ogms, 1); atomic_set(&bat_priv->bonding, 0); @@@ -540,49 -485,189 +485,189 @@@ bat_priv->primary_if = NULL; bat_priv->num_ifaces = 0;
- ret = batadv_algo_select(bat_priv, batadv_routing_algo); - if (ret < 0) - goto unreg_soft_iface; + batadv_nc_init_bat_priv(bat_priv);
- ret = batadv_sysfs_add_meshif(soft_iface); + ret = batadv_algo_select(bat_priv, batadv_routing_algo); if (ret < 0) - goto unreg_soft_iface; + goto free_bat_counters;
- ret = batadv_debugfs_add_meshif(soft_iface); + ret = batadv_debugfs_add_meshif(dev); if (ret < 0) - goto unreg_sysfs; + goto free_bat_counters;
- ret = batadv_mesh_init(soft_iface); + ret = batadv_mesh_init(dev); if (ret < 0) goto unreg_debugfs;
- return soft_iface; + return 0;
unreg_debugfs: - batadv_debugfs_del_meshif(soft_iface); - unreg_sysfs: - batadv_sysfs_del_meshif(soft_iface); - unreg_soft_iface: - free_percpu(bat_priv->bat_counters); - unregister_netdevice(soft_iface); - return NULL; - + batadv_debugfs_del_meshif(dev); free_bat_counters: free_percpu(bat_priv->bat_counters); - free_soft_iface: - free_netdev(soft_iface); + + 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: - return NULL; + if (hard_iface) + batadv_hardif_free_ref(hard_iface); + return ret; }
- void batadv_softif_destroy(struct net_device *soft_iface) + /** + * 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); + 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) @@@ -591,6 -676,13 +676,13 @@@ 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) { @@@ -662,6 -754,17 +754,17 @@@ static const struct { "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, diff --combined net/batman-adv/soft-interface.h index 43182e5,2f2472c..2f2472c --- a/net/batman-adv/soft-interface.h +++ b/net/batman-adv/soft-interface.h @@@ -25,7 -25,8 +25,8 @@@ void batadv_interface_rx(struct net_dev 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(struct net_device *soft_iface); + 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 --combined net/batman-adv/sysfs.c index afbba31,15a22ef..15a22ef --- a/net/batman-adv/sysfs.c +++ b/net/batman-adv/sysfs.c @@@ -442,6 -442,9 +442,9 @@@ static BATADV_ATTR(gw_bandwidth, S_IRUG #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, @@@ -464,6 -467,9 +467,9 @@@ #ifdef CONFIG_BATMAN_ADV_DEBUG &batadv_attr_log_level, #endif + #ifdef CONFIG_BATMAN_ADV_NC + &batadv_attr_network_coding, + #endif NULL, };
@@@ -582,13 -588,15 +588,15 @@@ static ssize_t batadv_store_mesh_iface( }
if (status_tmp == BATADV_IF_NOT_IN_USE) { - batadv_hardif_disable_interface(hard_iface); + 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_hardif_disable_interface(hard_iface, + BATADV_IF_CLEANUP_AUTO);
ret = batadv_hardif_enable_interface(hard_iface, buff);
@@@ -688,15 -696,10 +696,10 @@@ int batadv_throw_uevent(struct batadv_p enum batadv_uev_action action, const char *data) { int ret = -ENOMEM; struct kobject *bat_kobj; char *uevent_env[4] = { NULL, NULL, NULL, NULL };
- primary_if = batadv_primary_if_get_selected(bat_priv); - if (!primary_if) - goto out; - - bat_kobj = &primary_if->soft_iface->dev.kobj; + bat_kobj = &bat_priv->soft_iface->dev.kobj;
uevent_env[0] = kmalloc(strlen(BATADV_UEV_TYPE_VAR) + strlen(batadv_uev_type_str[type]) + 1, @@@ -732,9 -735,6 +735,6 @@@ out kfree(uevent_env[1]); kfree(uevent_env[2]);
- if (primary_if) - batadv_hardif_free_ref(primary_if); - if (ret) batadv_dbg(BATADV_DBG_BATMAN, bat_priv, "Impossible to send uevent for (%s,%s,%s) event (err: %d)\n", diff --combined net/batman-adv/translation-table.c index 98a66a0,0000000..9322320 mode 100644,000000..100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@@ -1,2586 -1,0 +1,2573 @@@ +/* 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); + batadv_orig_node_free_ref(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) +{ - struct batadv_hard_iface *primary_if; + int req_len; + - primary_if = batadv_primary_if_get_selected(bat_priv); - + 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 ((!primary_if) || (req_len > primary_if->soft_iface->mtu)) ++ 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); - - if (primary_if) - batadv_hardif_free_ref(primary_if); +} + +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)); ++ 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_hard_iface *primary_if, ++ 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 > primary_if->soft_iface->mtu) { - tt_len = primary_if->soft_iface->mtu - tt_query_size; ++ 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; - struct batadv_hard_iface *primary_if = 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; + - primary_if = batadv_primary_if_get_selected(bat_priv); - if (!primary_if) - 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, - primary_if, ++ 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 (primary_if) - batadv_hardif_free_ref(primary_if); + 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, - primary_if, ++ 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(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); ++ 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 --combined net/batman-adv/types.h index 4cd87a0,aba8364..aba8364 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@@ -128,6 -128,10 +128,10 @@@ struct batadv_hard_iface * @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]; @@@ -171,6 -175,12 +175,12 @@@ 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 };
/** @@@ -265,6 -275,17 +275,17 @@@ struct batadv_bcast_duplist_entry * @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 { @@@ -292,6 -313,17 +313,17 @@@ 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, };
@@@ -428,6 -460,35 +460,35 @@@ struct batadv_priv_dat #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 @@@ -470,6 -531,8 +531,8 @@@ * @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; @@@ -522,6 -585,10 +585,10 @@@ #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 */ };
/** @@@ -702,6 -769,75 +769,75 @@@ struct batadv_tt_roam_node };
/** + * 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) diff --combined net/batman-adv/unicast.c index 50e079f,0bb3b59..0bb3b59 --- a/net/batman-adv/unicast.c +++ b/net/batman-adv/unicast.c @@@ -122,7 -122,7 +122,7 @@@ batadv_frag_search_packet(struct list_h { struct batadv_frag_packet_list_entry *tfp; struct batadv_unicast_frag_packet *tmp_up = NULL; - int is_head_tmp, is_head; + bool is_head_tmp, is_head; uint16_t search_seqno;
if (up->flags & BATADV_UNI_FRAG_HEAD) @@@ -130,7 -130,7 +130,7 @@@ else search_seqno = ntohs(up->seqno)-1;
- is_head = !!(up->flags & BATADV_UNI_FRAG_HEAD); + is_head = up->flags & BATADV_UNI_FRAG_HEAD;
list_for_each_entry(tfp, head, list) { if (!tfp->skb) @@@ -142,7 -142,7 +142,7 @@@ 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); + is_head_tmp = tmp_up->flags & BATADV_UNI_FRAG_HEAD; if (is_head_tmp != is_head) return tfp; else