Skip to content

Commit

Permalink
Revert "net: hsr: use hlist_head instead of list_head for mac addresses"
Browse files Browse the repository at this point in the history
[ Upstream commit e012764 ]

The hlist optimisation (which not only uses hlist_head instead of
list_head but also splits hsr_priv::node_db into an array of 256 slots)
does not consider the "node merge":
Upon starting the hsr network (with three nodes) a packet that is
sent from node1 to node3 will also be sent from node1 to node2 and then
forwarded to node3.
As a result node3 will receive 2 packets because it is not able
to filter out the duplicate. Each packet received will create a new
struct hsr_node with macaddress_A only set the MAC address it received
from (the two MAC addesses from node1).
At some point (early in the process) two supervision frames will be
received from node1. They will be processed by hsr_handle_sup_frame()
and one frame will leave early ("Node has already been merged") and does
nothing. The other frame will be merged as portB and have its MAC
address written to macaddress_B and the hsr_node (that was created for
it as macaddress_A) will be removed.
From now on HSR is able to identify a duplicate because both packets
sent from one node will result in the same struct hsr_node because
hsr_get_node() will find the MAC address either on macaddress_A or
macaddress_B.

Things get tricky with the optimisation: If sender's MAC address is
saved as macaddress_A then the lookup will work as usual. If the MAC
address has been merged into macaddress_B of another hsr_node then the
lookup won't work because it is likely that the data structure is in
another bucket. This results in creating a new struct hsr_node and not
recognising a possible duplicate.

A way around it would be to add another hsr_node::mac_list_B and attach
it to the other bucket to ensure that this hsr_node will be looked up
either via macaddress_A _or_ macaddress_B.

I however prefer to revert it because it sounds like an academic problem
rather than real life workload plus it adds complexity. I'm not an HSR
expert with what is usual size of a network but I would guess 40 to 60
nodes. With 10.000 nodes and assuming 60us for pass-through (from node
to node) then it would take almost 600ms for a packet to almost wrap
around which sounds a lot.

Revert the hash MAC addresses optimisation.

Fixes: 4acc45d ("net: hsr: use hlist_head instead of list_head for mac addresses")
Cc: Juhee Kang <claudiajkang@gmail.com>
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Sebastian Andrzej Siewior authored and gregkh committed Dec 31, 2022
1 parent 35a6ae2 commit 41d4bcc
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 187 deletions.
40 changes: 16 additions & 24 deletions net/hsr/hsr_debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/debugfs.h>
#include <linux/jhash.h>
#include "hsr_main.h"
#include "hsr_framereg.h"

Expand All @@ -21,7 +20,6 @@ hsr_node_table_show(struct seq_file *sfp, void *data)
{
struct hsr_priv *priv = (struct hsr_priv *)sfp->private;
struct hsr_node *node;
int i;

seq_printf(sfp, "Node Table entries for (%s) device\n",
(priv->prot_version == PRP_V1 ? "PRP" : "HSR"));
Expand All @@ -33,28 +31,22 @@ hsr_node_table_show(struct seq_file *sfp, void *data)
seq_puts(sfp, "DAN-H\n");

rcu_read_lock();

for (i = 0 ; i < priv->hash_buckets; i++) {
hlist_for_each_entry_rcu(node, &priv->node_db[i], mac_list) {
/* skip self node */
if (hsr_addr_is_self(priv, node->macaddress_A))
continue;
seq_printf(sfp, "%pM ", &node->macaddress_A[0]);
seq_printf(sfp, "%pM ", &node->macaddress_B[0]);
seq_printf(sfp, "%10lx, ",
node->time_in[HSR_PT_SLAVE_A]);
seq_printf(sfp, "%10lx, ",
node->time_in[HSR_PT_SLAVE_B]);
seq_printf(sfp, "%14x, ", node->addr_B_port);

if (priv->prot_version == PRP_V1)
seq_printf(sfp, "%5x, %5x, %5x\n",
node->san_a, node->san_b,
(node->san_a == 0 &&
node->san_b == 0));
else
seq_printf(sfp, "%5x\n", 1);
}
list_for_each_entry_rcu(node, &priv->node_db, mac_list) {
/* skip self node */
if (hsr_addr_is_self(priv, node->macaddress_A))
continue;
seq_printf(sfp, "%pM ", &node->macaddress_A[0]);
seq_printf(sfp, "%pM ", &node->macaddress_B[0]);
seq_printf(sfp, "%10lx, ", node->time_in[HSR_PT_SLAVE_A]);
seq_printf(sfp, "%10lx, ", node->time_in[HSR_PT_SLAVE_B]);
seq_printf(sfp, "%14x, ", node->addr_B_port);

if (priv->prot_version == PRP_V1)
seq_printf(sfp, "%5x, %5x, %5x\n",
node->san_a, node->san_b,
(node->san_a == 0 && node->san_b == 0));
else
seq_printf(sfp, "%5x\n", 1);
}
rcu_read_unlock();
return 0;
Expand Down
10 changes: 3 additions & 7 deletions net/hsr/hsr_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,16 +485,12 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2],
{
bool unregister = false;
struct hsr_priv *hsr;
int res, i;
int res;

hsr = netdev_priv(hsr_dev);
INIT_LIST_HEAD(&hsr->ports);
INIT_HLIST_HEAD(&hsr->self_node_db);
hsr->hash_buckets = HSR_HSIZE;
get_random_bytes(&hsr->hash_seed, sizeof(hsr->hash_seed));
for (i = 0; i < hsr->hash_buckets; i++)
INIT_HLIST_HEAD(&hsr->node_db[i]);

INIT_LIST_HEAD(&hsr->node_db);
INIT_LIST_HEAD(&hsr->self_node_db);
spin_lock_init(&hsr->list_lock);

eth_hw_addr_set(hsr_dev, slave[0]->dev_addr);
Expand Down
7 changes: 2 additions & 5 deletions net/hsr/hsr_forward.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,23 +571,20 @@ static int fill_frame_info(struct hsr_frame_info *frame,
struct ethhdr *ethhdr;
__be16 proto;
int ret;
u32 hash;

/* Check if skb contains ethhdr */
if (skb->mac_len < sizeof(struct ethhdr))
return -EINVAL;

memset(frame, 0, sizeof(*frame));

ethhdr = (struct ethhdr *)skb_mac_header(skb);
hash = hsr_mac_hash(port->hsr, ethhdr->h_source);
frame->is_supervision = is_supervision_frame(port->hsr, skb);
frame->node_src = hsr_get_node(port, &hsr->node_db[hash], skb,
frame->node_src = hsr_get_node(port, &hsr->node_db, skb,
frame->is_supervision,
port->type);
if (!frame->node_src)
return -1; /* Unknown node and !is_supervision, or no mem */

ethhdr = (struct ethhdr *)skb_mac_header(skb);
frame->is_vlan = false;
proto = ethhdr->h_proto;

Expand Down

0 comments on commit 41d4bcc

Please sign in to comment.