diff options
Diffstat (limited to 'net/hsr/hsr_framereg.c')
| -rw-r--r-- | net/hsr/hsr_framereg.c | 499 | 
1 files changed, 499 insertions, 0 deletions
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c new file mode 100644 index 00000000000..83e58449366 --- /dev/null +++ b/net/hsr/hsr_framereg.c @@ -0,0 +1,499 @@ +/* Copyright 2011-2013 Autronica Fire and Security AS + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * Author(s): + *	2011-2013 Arvid Brodin, arvid.brodin@xdin.com + * + * The HSR spec says never to forward the same frame twice on the same + * interface. A frame is identified by its source MAC address and its HSR + * sequence number. This code keeps track of senders and their sequence numbers + * to allow filtering of duplicate frames, and to detect HSR ring errors. + */ + +#include <linux/if_ether.h> +#include <linux/etherdevice.h> +#include <linux/slab.h> +#include <linux/rculist.h> +#include "hsr_main.h" +#include "hsr_framereg.h" +#include "hsr_netlink.h" + + +struct node_entry { +	struct list_head mac_list; +	unsigned char	MacAddressA[ETH_ALEN]; +	unsigned char	MacAddressB[ETH_ALEN]; +	enum hsr_dev_idx   AddrB_if;	/* The local slave through which AddrB +					 * frames are received from this node +					 */ +	unsigned long	time_in[HSR_MAX_SLAVE]; +	bool		time_in_stale[HSR_MAX_SLAVE]; +	u16		seq_out[HSR_MAX_DEV]; +	struct rcu_head rcu_head; +}; + +/*	TODO: use hash lists for mac addresses (linux/jhash.h)?    */ + + + +/* Search for mac entry. Caller must hold rcu read lock. + */ +static struct node_entry *find_node_by_AddrA(struct list_head *node_db, +					     const unsigned char addr[ETH_ALEN]) +{ +	struct node_entry *node; + +	list_for_each_entry_rcu(node, node_db, mac_list) { +		if (ether_addr_equal(node->MacAddressA, addr)) +			return node; +	} + +	return NULL; +} + + +/* Search for mac entry. Caller must hold rcu read lock. + */ +static struct node_entry *find_node_by_AddrB(struct list_head *node_db, +					     const unsigned char addr[ETH_ALEN]) +{ +	struct node_entry *node; + +	list_for_each_entry_rcu(node, node_db, mac_list) { +		if (ether_addr_equal(node->MacAddressB, addr)) +			return node; +	} + +	return NULL; +} + + +/* Search for mac entry. Caller must hold rcu read lock. + */ +struct node_entry *hsr_find_node(struct list_head *node_db, struct sk_buff *skb) +{ +	struct node_entry *node; +	struct ethhdr *ethhdr; + +	if (!skb_mac_header_was_set(skb)) +		return NULL; + +	ethhdr = (struct ethhdr *) skb_mac_header(skb); + +	list_for_each_entry_rcu(node, node_db, mac_list) { +		if (ether_addr_equal(node->MacAddressA, ethhdr->h_source)) +			return node; +		if (ether_addr_equal(node->MacAddressB, ethhdr->h_source)) +			return node; +	} + +	return NULL; +} + + +/* Helper for device init; the self_node_db is used in hsr_rcv() to recognize + * frames from self that's been looped over the HSR ring. + */ +int hsr_create_self_node(struct list_head *self_node_db, +			 unsigned char addr_a[ETH_ALEN], +			 unsigned char addr_b[ETH_ALEN]) +{ +	struct node_entry *node, *oldnode; + +	node = kmalloc(sizeof(*node), GFP_KERNEL); +	if (!node) +		return -ENOMEM; + +	ether_addr_copy(node->MacAddressA, addr_a); +	ether_addr_copy(node->MacAddressB, addr_b); + +	rcu_read_lock(); +	oldnode = list_first_or_null_rcu(self_node_db, +						struct node_entry, mac_list); +	if (oldnode) { +		list_replace_rcu(&oldnode->mac_list, &node->mac_list); +		rcu_read_unlock(); +		synchronize_rcu(); +		kfree(oldnode); +	} else { +		rcu_read_unlock(); +		list_add_tail_rcu(&node->mac_list, self_node_db); +	} + +	return 0; +} + + +/* Add/merge node to the database of nodes. 'skb' must contain an HSR + * supervision frame. + * - If the supervision header's MacAddressA field is not yet in the database, + * this frame is from an hitherto unknown node - add it to the database. + * - If the sender's MAC address is not the same as its MacAddressA address, + * the node is using PICS_SUBS (address substitution). Record the sender's + * address as the node's MacAddressB. + * + * This function needs to work even if the sender node has changed one of its + * slaves' MAC addresses. In this case, there are four different cases described + * by (Addr-changed, received-from) pairs as follows. Note that changing the + * SlaveA address is equal to changing the node's own address: + * + * - (AddrB, SlaveB): The new AddrB will be recorded by PICS_SUBS code since + *		      node == NULL. + * - (AddrB, SlaveA): Will work as usual (the AddrB change won't be detected + *		      from this frame). + * + * - (AddrA, SlaveB): The old node will be found. We need to detect this and + *		      remove the node. + * - (AddrA, SlaveA): A new node will be registered (non-PICS_SUBS at first). + *		      The old one will be pruned after HSR_NODE_FORGET_TIME. + * + * We also need to detect if the sender's SlaveA and SlaveB cables have been + * swapped. + */ +struct node_entry *hsr_merge_node(struct hsr_priv *hsr_priv, +				  struct node_entry *node, +				  struct sk_buff *skb, +				  enum hsr_dev_idx dev_idx) +{ +	struct hsr_sup_payload *hsr_sp; +	struct hsr_ethhdr_sp *hsr_ethsup; +	int i; +	unsigned long now; + +	hsr_ethsup = (struct hsr_ethhdr_sp *) skb_mac_header(skb); +	hsr_sp = (struct hsr_sup_payload *) skb->data; + +	if (node && !ether_addr_equal(node->MacAddressA, hsr_sp->MacAddressA)) { +		/* Node has changed its AddrA, frame was received from SlaveB */ +		list_del_rcu(&node->mac_list); +		kfree_rcu(node, rcu_head); +		node = NULL; +	} + +	if (node && (dev_idx == node->AddrB_if) && +	    !ether_addr_equal(node->MacAddressB, hsr_ethsup->ethhdr.h_source)) { +		/* Cables have been swapped */ +		list_del_rcu(&node->mac_list); +		kfree_rcu(node, rcu_head); +		node = NULL; +	} + +	if (node && (dev_idx != node->AddrB_if) && +	    (node->AddrB_if != HSR_DEV_NONE) && +	    !ether_addr_equal(node->MacAddressA, hsr_ethsup->ethhdr.h_source)) { +		/* Cables have been swapped */ +		list_del_rcu(&node->mac_list); +		kfree_rcu(node, rcu_head); +		node = NULL; +	} + +	if (node) +		return node; + +	node = find_node_by_AddrA(&hsr_priv->node_db, hsr_sp->MacAddressA); +	if (node) { +		/* Node is known, but frame was received from an unknown +		 * address. Node is PICS_SUBS capable; merge its AddrB. +		 */ +		ether_addr_copy(node->MacAddressB, hsr_ethsup->ethhdr.h_source); +		node->AddrB_if = dev_idx; +		return node; +	} + +	node = kzalloc(sizeof(*node), GFP_ATOMIC); +	if (!node) +		return NULL; + +	ether_addr_copy(node->MacAddressA, hsr_sp->MacAddressA); +	ether_addr_copy(node->MacAddressB, hsr_ethsup->ethhdr.h_source); +	if (!ether_addr_equal(hsr_sp->MacAddressA, hsr_ethsup->ethhdr.h_source)) +		node->AddrB_if = dev_idx; +	else +		node->AddrB_if = HSR_DEV_NONE; + +	/* We are only interested in time diffs here, so use current jiffies +	 * as initialization. (0 could trigger an spurious ring error warning). +	 */ +	now = jiffies; +	for (i = 0; i < HSR_MAX_SLAVE; i++) +		node->time_in[i] = now; +	for (i = 0; i < HSR_MAX_DEV; i++) +		node->seq_out[i] = ntohs(hsr_ethsup->hsr_sup.sequence_nr) - 1; + +	list_add_tail_rcu(&node->mac_list, &hsr_priv->node_db); + +	return node; +} + + +/* 'skb' is a frame meant for this host, that is to be passed to upper layers. + * + * If the frame was sent by a node's B interface, replace the sender + * address with that node's "official" address (MacAddressA) so that upper + * layers recognize where it came from. + */ +void hsr_addr_subst_source(struct hsr_priv *hsr_priv, struct sk_buff *skb) +{ +	struct ethhdr *ethhdr; +	struct node_entry *node; + +	if (!skb_mac_header_was_set(skb)) { +		WARN_ONCE(1, "%s: Mac header not set\n", __func__); +		return; +	} +	ethhdr = (struct ethhdr *) skb_mac_header(skb); + +	rcu_read_lock(); +	node = find_node_by_AddrB(&hsr_priv->node_db, ethhdr->h_source); +	if (node) +		ether_addr_copy(ethhdr->h_source, node->MacAddressA); +	rcu_read_unlock(); +} + + +/* 'skb' is a frame meant for another host. + * 'hsr_dev_idx' is the HSR index of the outgoing device + * + * Substitute the target (dest) MAC address if necessary, so the it matches the + * recipient interface MAC address, regardless of whether that is the + * recipient's A or B interface. + * This is needed to keep the packets flowing through switches that learn on + * which "side" the different interfaces are. + */ +void hsr_addr_subst_dest(struct hsr_priv *hsr_priv, struct ethhdr *ethhdr, +			 enum hsr_dev_idx dev_idx) +{ +	struct node_entry *node; + +	rcu_read_lock(); +	node = find_node_by_AddrA(&hsr_priv->node_db, ethhdr->h_dest); +	if (node && (node->AddrB_if == dev_idx)) +		ether_addr_copy(ethhdr->h_dest, node->MacAddressB); +	rcu_read_unlock(); +} + + +/* seq_nr_after(a, b) - return true if a is after (higher in sequence than) b, + * false otherwise. + */ +static bool seq_nr_after(u16 a, u16 b) +{ +	/* Remove inconsistency where +	 * seq_nr_after(a, b) == seq_nr_before(a, b) +	 */ +	if ((int) b - a == 32768) +		return false; + +	return (((s16) (b - a)) < 0); +} +#define seq_nr_before(a, b)		seq_nr_after((b), (a)) +#define seq_nr_after_or_eq(a, b)	(!seq_nr_before((a), (b))) +#define seq_nr_before_or_eq(a, b)	(!seq_nr_after((a), (b))) + + +void hsr_register_frame_in(struct node_entry *node, enum hsr_dev_idx dev_idx) +{ +	if ((dev_idx < 0) || (dev_idx >= HSR_MAX_SLAVE)) { +		WARN_ONCE(1, "%s: Invalid dev_idx (%d)\n", __func__, dev_idx); +		return; +	} +	node->time_in[dev_idx] = jiffies; +	node->time_in_stale[dev_idx] = false; +} + + +/* 'skb' is a HSR Ethernet frame (with a HSR tag inserted), with a valid + * ethhdr->h_source address and skb->mac_header set. + * + * Return: + *	 1 if frame can be shown to have been sent recently on this interface, + *	 0 otherwise, or + *	 negative error code on error + */ +int hsr_register_frame_out(struct node_entry *node, enum hsr_dev_idx dev_idx, +			   struct sk_buff *skb) +{ +	struct hsr_ethhdr *hsr_ethhdr; +	u16 sequence_nr; + +	if ((dev_idx < 0) || (dev_idx >= HSR_MAX_DEV)) { +		WARN_ONCE(1, "%s: Invalid dev_idx (%d)\n", __func__, dev_idx); +		return -EINVAL; +	} +	if (!skb_mac_header_was_set(skb)) { +		WARN_ONCE(1, "%s: Mac header not set\n", __func__); +		return -EINVAL; +	} +	hsr_ethhdr = (struct hsr_ethhdr *) skb_mac_header(skb); + +	sequence_nr = ntohs(hsr_ethhdr->hsr_tag.sequence_nr); +	if (seq_nr_before_or_eq(sequence_nr, node->seq_out[dev_idx])) +		return 1; + +	node->seq_out[dev_idx] = sequence_nr; +	return 0; +} + + + +static bool is_late(struct node_entry *node, enum hsr_dev_idx dev_idx) +{ +	enum hsr_dev_idx other; + +	if (node->time_in_stale[dev_idx]) +		return true; + +	if (dev_idx == HSR_DEV_SLAVE_A) +		other = HSR_DEV_SLAVE_B; +	else +		other = HSR_DEV_SLAVE_A; + +	if (node->time_in_stale[other]) +		return false; + +	if (time_after(node->time_in[other], node->time_in[dev_idx] + +		       msecs_to_jiffies(MAX_SLAVE_DIFF))) +		return true; + +	return false; +} + + +/* Remove stale sequence_nr records. Called by timer every + * HSR_LIFE_CHECK_INTERVAL (two seconds or so). + */ +void hsr_prune_nodes(struct hsr_priv *hsr_priv) +{ +	struct node_entry *node; +	unsigned long timestamp; +	unsigned long time_a, time_b; + +	rcu_read_lock(); +	list_for_each_entry_rcu(node, &hsr_priv->node_db, mac_list) { +		/* Shorthand */ +		time_a = node->time_in[HSR_DEV_SLAVE_A]; +		time_b = node->time_in[HSR_DEV_SLAVE_B]; + +		/* Check for timestamps old enough to risk wrap-around */ +		if (time_after(jiffies, time_a + MAX_JIFFY_OFFSET/2)) +			node->time_in_stale[HSR_DEV_SLAVE_A] = true; +		if (time_after(jiffies, time_b + MAX_JIFFY_OFFSET/2)) +			node->time_in_stale[HSR_DEV_SLAVE_B] = true; + +		/* Get age of newest frame from node. +		 * At least one time_in is OK here; nodes get pruned long +		 * before both time_ins can get stale +		 */ +		timestamp = time_a; +		if (node->time_in_stale[HSR_DEV_SLAVE_A] || +		    (!node->time_in_stale[HSR_DEV_SLAVE_B] && +		    time_after(time_b, time_a))) +			timestamp = time_b; + +		/* Warn of ring error only as long as we get frames at all */ +		if (time_is_after_jiffies(timestamp + +					msecs_to_jiffies(1.5*MAX_SLAVE_DIFF))) { + +			if (is_late(node, HSR_DEV_SLAVE_A)) +				hsr_nl_ringerror(hsr_priv, node->MacAddressA, +						 HSR_DEV_SLAVE_A); +			else if (is_late(node, HSR_DEV_SLAVE_B)) +				hsr_nl_ringerror(hsr_priv, node->MacAddressA, +						 HSR_DEV_SLAVE_B); +		} + +		/* Prune old entries */ +		if (time_is_before_jiffies(timestamp + +					msecs_to_jiffies(HSR_NODE_FORGET_TIME))) { +			hsr_nl_nodedown(hsr_priv, node->MacAddressA); +			list_del_rcu(&node->mac_list); +			/* Note that we need to free this entry later: */ +			kfree_rcu(node, rcu_head); +		} +	} +	rcu_read_unlock(); +} + + +void *hsr_get_next_node(struct hsr_priv *hsr_priv, void *_pos, +			unsigned char addr[ETH_ALEN]) +{ +	struct node_entry *node; + +	if (!_pos) { +		node = list_first_or_null_rcu(&hsr_priv->node_db, +						struct node_entry, mac_list); +		if (node) +			ether_addr_copy(addr, node->MacAddressA); +		return node; +	} + +	node = _pos; +	list_for_each_entry_continue_rcu(node, &hsr_priv->node_db, mac_list) { +		ether_addr_copy(addr, node->MacAddressA); +		return node; +	} + +	return NULL; +} + + +int hsr_get_node_data(struct hsr_priv *hsr_priv, +		      const unsigned char *addr, +		      unsigned char addr_b[ETH_ALEN], +		      unsigned int *addr_b_ifindex, +		      int *if1_age, +		      u16 *if1_seq, +		      int *if2_age, +		      u16 *if2_seq) +{ +	struct node_entry *node; +	unsigned long tdiff; + + +	rcu_read_lock(); +	node = find_node_by_AddrA(&hsr_priv->node_db, addr); +	if (!node) { +		rcu_read_unlock(); +		return -ENOENT;	/* No such entry */ +	} + +	ether_addr_copy(addr_b, node->MacAddressB); + +	tdiff = jiffies - node->time_in[HSR_DEV_SLAVE_A]; +	if (node->time_in_stale[HSR_DEV_SLAVE_A]) +		*if1_age = INT_MAX; +#if HZ <= MSEC_PER_SEC +	else if (tdiff > msecs_to_jiffies(INT_MAX)) +		*if1_age = INT_MAX; +#endif +	else +		*if1_age = jiffies_to_msecs(tdiff); + +	tdiff = jiffies - node->time_in[HSR_DEV_SLAVE_B]; +	if (node->time_in_stale[HSR_DEV_SLAVE_B]) +		*if2_age = INT_MAX; +#if HZ <= MSEC_PER_SEC +	else if (tdiff > msecs_to_jiffies(INT_MAX)) +		*if2_age = INT_MAX; +#endif +	else +		*if2_age = jiffies_to_msecs(tdiff); + +	/* Present sequence numbers as if they were incoming on interface */ +	*if1_seq = node->seq_out[HSR_DEV_SLAVE_B]; +	*if2_seq = node->seq_out[HSR_DEV_SLAVE_A]; + +	if ((node->AddrB_if != HSR_DEV_NONE) && hsr_priv->slave[node->AddrB_if]) +		*addr_b_ifindex = hsr_priv->slave[node->AddrB_if]->ifindex; +	else +		*addr_b_ifindex = -1; + +	rcu_read_unlock(); + +	return 0; +}  | 
