Skip to content

Commit aae9d6b

Browse files
fmaurer-rhPaolo Abeni
authored andcommitted
hsr: Implement more robust duplicate discard for HSR
The HSR duplicate discard algorithm had even more basic problems than the described for PRP in the previous patch. It relied only on the last received sequence number to decide if a new frame should be forwarded to any port. This does not work correctly in any case where frames are received out of order. The linked bug report claims that this can even happen with perfectly fine links due to the order in which incoming frames are processed (which can be unexpected on multi-core systems). The issue also occasionally shows up in the HSR selftests. The main reason is that the sequence number that was last forwarded to the master port may have skipped a number which will in turn never be delivered to the host. As the problem (we accidentally skip over a sequence number that has not been received but will be received in the future) is similar to PRP, we can apply a similar solution. The duplicate discard algorithm based on the "sparse bitmap" works well for HSR if it is extended to track one bitmap for each port (A, B, master, interlink). To do this, change the sequence number blocks to contain a flexible array member as the last member that can keep chunks for as many bitmaps as we need. This design makes it easy to reuse the same algorithm in a potential PRP RedBox implementation. The duplicate discard algorithm functions are modified to deal with sequence number blocks of different sizes and to correctly use the array of bitmap chunks. There is a notable speciality for HSR: the port type has a special port type NONE with value 0. This leads to the number of port types being 5 instead of actually 4. To save memory, remove the NONE port from the bitmap (by subtracting 1) when setting up the block buffer and when accessing the bitmap chunks in the array. Removing the old algorithm allows us to get rid of a few fields that are not needed any more: time_out and seq_out for each port. We can also remove some functions that were only necessary for the previous duplicate discard algorithm. The removal of seq_out is possible despite its previous usage in hsr_register_frame_in: it was used to prevent updates to time_in when "invalid" sequence numbers were received. With the new duplicate discard algorithm, time_in has no relevance for the expiry of sequence numbers anymore. They will expire based on the timestamps in the sequence number blocks after at most 400ms. There is no need that a node "re-registers" to "resume communication": after 400ms, all sequence numbers are accepted again. Also, according to the IEC 62439-3:2021, all nodes are supposed to send no traffic for 500ms after boot to lead exactly to this expiry of seen sequence numbers. time_in is still used for pruning nodes from the node table after no traffic has been received for 60sec. Pruning is only needed if the node is really gone and has not been sending any traffic for that period. seq_out was also used to report the last incoming sequence number from a node through netlink. I am not sure how useful this value is to userspace at all, but added getting it from the sequence number blocks. This number can be outdated after node merging until a new block has been added. Update the KUnit test for the PRP duplicate discard so that the node allocation matches and expectations on the removed fields are removed. Reported-by: Yoann Congal <yoann.congal@smile.fr> Closes: https://lore.kernel.org/netdev/7d221a07-8358-4c0b-a09c-3b029c052245@smile.fr/ Signed-off-by: Felix Maurer <fmaurer@redhat.com> Reviewed-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Link: https://patch.msgid.link/36dc3bc5bdb7e68b70bb5ef86f53ca95a3f35418.1770299429.git.fmaurer@redhat.com Signed-off-by: Paolo Abeni <pabeni@redhat.com>
1 parent 8908c3c commit aae9d6b

3 files changed

Lines changed: 131 additions & 143 deletions

File tree

net/hsr/hsr_framereg.c

Lines changed: 110 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,6 @@
2020
#include "hsr_framereg.h"
2121
#include "hsr_netlink.h"
2222

23-
/* seq_nr_after(a, b) - return true if a is after (higher in sequence than) b,
24-
* false otherwise.
25-
*/
26-
static bool seq_nr_after(u16 a, u16 b)
27-
{
28-
/* Remove inconsistency where
29-
* seq_nr_after(a, b) == seq_nr_before(a, b)
30-
*/
31-
if ((int)b - a == 32768)
32-
return false;
33-
34-
return (((s16)(b - a)) < 0);
35-
}
36-
37-
#define seq_nr_before(a, b) seq_nr_after((b), (a))
38-
#define seq_nr_before_or_eq(a, b) (!seq_nr_after((a), (b)))
39-
4023
bool hsr_addr_is_redbox(struct hsr_priv *hsr, unsigned char *addr)
4124
{
4225
if (!hsr->redbox || !is_valid_ether_addr(hsr->macaddress_redbox))
@@ -164,18 +147,16 @@ void prp_handle_san_frame(bool san, enum hsr_port_type port,
164147
node->san_b = true;
165148
}
166149

167-
/* Allocate an hsr_node and add it to node_db. 'addr' is the node's address_A;
168-
* seq_out is used to initialize filtering of outgoing duplicate frames
169-
* originating from the newly added node.
150+
/* Allocate an hsr_node and add it to node_db. 'addr' is the node's address_A.
170151
*/
171152
static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
172153
struct list_head *node_db,
173-
unsigned char addr[],
174-
u16 seq_out, bool san,
154+
unsigned char addr[], bool san,
175155
enum hsr_port_type rx_port)
176156
{
177157
struct hsr_node *new_node, *node = NULL;
178158
unsigned long now;
159+
size_t block_sz;
179160
int i;
180161

181162
new_node = kzalloc(sizeof(*new_node), GFP_ATOMIC);
@@ -185,9 +166,13 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
185166
ether_addr_copy(new_node->macaddress_A, addr);
186167
spin_lock_init(&new_node->seq_out_lock);
187168

188-
new_node->block_buf = kcalloc(HSR_MAX_SEQ_BLOCKS,
189-
sizeof(struct hsr_seq_block),
190-
GFP_ATOMIC);
169+
if (hsr->prot_version == PRP_V1)
170+
new_node->seq_port_cnt = 1;
171+
else
172+
new_node->seq_port_cnt = HSR_PT_PORTS - 1;
173+
174+
block_sz = hsr_seq_block_size(new_node);
175+
new_node->block_buf = kcalloc(HSR_MAX_SEQ_BLOCKS, block_sz, GFP_ATOMIC);
191176
if (!new_node->block_buf)
192177
goto free;
193178

@@ -199,8 +184,6 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
199184
now = jiffies;
200185
for (i = 0; i < HSR_PT_PORTS; i++) {
201186
new_node->time_in[i] = now;
202-
new_node->time_out[i] = now;
203-
new_node->seq_out[i] = seq_out;
204187
}
205188

206189
if (san && hsr->proto_ops->handle_san_frame)
@@ -245,7 +228,6 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
245228
struct ethhdr *ethhdr;
246229
struct prp_rct *rct;
247230
bool san = false;
248-
u16 seq_out;
249231

250232
if (!skb_mac_header_was_set(skb))
251233
return NULL;
@@ -282,24 +264,13 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
282264
/* Check if skb contains hsr_ethhdr */
283265
if (skb->mac_len < sizeof(struct hsr_ethhdr))
284266
return NULL;
285-
286-
/* Use the existing sequence_nr from the tag as starting point
287-
* for filtering duplicate frames.
288-
*/
289-
seq_out = hsr_get_skb_sequence_nr(skb) - 1;
290267
} else {
291268
rct = skb_get_PRP_rct(skb);
292-
if (rct && prp_check_lsdu_size(skb, rct, is_sup)) {
293-
seq_out = prp_get_skb_sequence_nr(rct);
294-
} else {
295-
if (rx_port != HSR_PT_MASTER)
296-
san = true;
297-
seq_out = HSR_SEQNR_START;
298-
}
269+
if (!rct && rx_port != HSR_PT_MASTER)
270+
san = true;
299271
}
300272

301-
return hsr_add_node(hsr, node_db, ethhdr->h_source, seq_out,
302-
san, rx_port);
273+
return hsr_add_node(hsr, node_db, ethhdr->h_source, san, rx_port);
303274
}
304275

305276
static bool hsr_seq_block_is_old(struct hsr_seq_block *block)
@@ -328,6 +299,7 @@ VISIBLE_IF_KUNIT struct hsr_seq_block *hsr_get_seq_block(struct hsr_node *node,
328299
u16 block_idx)
329300
{
330301
struct hsr_seq_block *block, *res;
302+
size_t block_sz;
331303

332304
block = xa_load(&node->seq_blocks, block_idx);
333305

@@ -337,10 +309,11 @@ VISIBLE_IF_KUNIT struct hsr_seq_block *hsr_get_seq_block(struct hsr_node *node,
337309
}
338310

339311
if (!block) {
340-
block = &node->block_buf[node->next_block];
312+
block_sz = hsr_seq_block_size(node);
313+
block = node->block_buf + node->next_block * block_sz;
341314
hsr_forget_seq_block(node, block);
342315

343-
memset(block, 0, sizeof(*block));
316+
memset(block, 0, block_sz);
344317
block->time = jiffies;
345318
block->block_idx = block_idx;
346319

@@ -420,8 +393,7 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
420393
if (!node_real)
421394
/* No frame received from AddrA of this node yet */
422395
node_real = hsr_add_node(hsr, node_db, hsr_sp->macaddress_A,
423-
HSR_SEQNR_START - 1, true,
424-
port_rcv->type);
396+
true, port_rcv->type);
425397
if (!node_real)
426398
goto done; /* No mem */
427399
if (node_real == node_curr)
@@ -468,8 +440,6 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
468440
node_real->time_in_stale[i] =
469441
node_curr->time_in_stale[i];
470442
}
471-
if (seq_nr_after(node_curr->seq_out[i], node_real->seq_out[i]))
472-
node_real->seq_out[i] = node_curr->seq_out[i];
473443
}
474444

475445
xa_for_each(&node_curr->seq_blocks, idx, src_blk) {
@@ -480,8 +450,10 @@ void hsr_handle_sup_frame(struct hsr_frame_info *frame)
480450
if (!merge_blk)
481451
continue;
482452
merge_blk->time = min(merge_blk->time, src_blk->time);
483-
bitmap_or(merge_blk->seq_nrs, merge_blk->seq_nrs,
484-
src_blk->seq_nrs, HSR_SEQ_BLOCK_SIZE);
453+
for (i = 0; i < node_real->seq_port_cnt; i++) {
454+
bitmap_or(merge_blk->seq_nrs[i], merge_blk->seq_nrs[i],
455+
src_blk->seq_nrs[i], HSR_SEQ_BLOCK_SIZE);
456+
}
485457
}
486458
spin_unlock_bh(&node_real->seq_out_lock);
487459
node_real->addr_B_port = port_rcv->type;
@@ -558,60 +530,28 @@ void hsr_addr_subst_dest(struct hsr_node *node_src, struct sk_buff *skb,
558530
void hsr_register_frame_in(struct hsr_node *node, struct hsr_port *port,
559531
u16 sequence_nr)
560532
{
561-
/* Don't register incoming frames without a valid sequence number. This
562-
* ensures entries of restarted nodes gets pruned so that they can
563-
* re-register and resume communications.
564-
*/
565-
if (!(port->dev->features & NETIF_F_HW_HSR_TAG_RM) &&
566-
seq_nr_before(sequence_nr, node->seq_out[port->type]))
567-
return;
568-
569533
node->time_in[port->type] = jiffies;
570534
node->time_in_stale[port->type] = false;
571535
}
572536

573-
/* 'skb' is a HSR Ethernet frame (with a HSR tag inserted), with a valid
574-
* ethhdr->h_source address and skb->mac_header set.
575-
*
576-
* Return:
577-
* 1 if frame can be shown to have been sent recently on this interface,
578-
* 0 otherwise, or
579-
* negative error code on error
580-
*/
581-
int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
582-
{
583-
struct hsr_node *node = frame->node_src;
584-
u16 sequence_nr = frame->sequence_nr;
585-
586-
spin_lock_bh(&node->seq_out_lock);
587-
if (seq_nr_before_or_eq(sequence_nr, node->seq_out[port->type]) &&
588-
time_is_after_jiffies(node->time_out[port->type] +
589-
msecs_to_jiffies(HSR_ENTRY_FORGET_TIME))) {
590-
spin_unlock_bh(&node->seq_out_lock);
591-
return 1;
592-
}
593-
594-
node->time_out[port->type] = jiffies;
595-
node->seq_out[port->type] = sequence_nr;
596-
spin_unlock_bh(&node->seq_out_lock);
597-
return 0;
598-
}
599-
600-
/* PRP duplicate discard algorithm: we maintain a bitmap where we set a bit for
537+
/* Duplicate discard algorithm: we maintain a bitmap where we set a bit for
601538
* every seen sequence number. The bitmap is split into blocks and the block
602539
* management is detailed in hsr_get_seq_block(). In any case, we err on the
603540
* side of accepting a packet, as the specification requires the algorithm to
604541
* be "designed such that it never rejects a legitimate frame, while occasional
605542
* acceptance of a duplicate can be tolerated." (IEC 62439-3:2021, 4.1.10.3).
543+
* While this requirement is explicit for PRP, applying it to HSR does no harm
544+
* either.
606545
*
607-
* 'port' is the outgoing interface
608546
* 'frame' is the frame to be sent
547+
* 'port_type' is the type of the outgoing interface
609548
*
610549
* Return:
611550
* 1 if frame can be shown to have been sent recently on this interface,
612551
* 0 otherwise
613552
*/
614-
int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
553+
static int hsr_check_duplicate(struct hsr_frame_info *frame,
554+
unsigned int port_type)
615555
{
616556
u16 sequence_nr, seq_bit, block_idx;
617557
struct hsr_seq_block *block;
@@ -620,20 +560,8 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
620560
node = frame->node_src;
621561
sequence_nr = frame->sequence_nr;
622562

623-
/* out-going frames are always in order */
624-
if (frame->port_rcv->type == HSR_PT_MASTER) {
625-
spin_lock_bh(&node->seq_out_lock);
626-
node->time_out[port->type] = jiffies;
627-
node->seq_out[port->type] = sequence_nr;
628-
spin_unlock_bh(&node->seq_out_lock);
563+
if (WARN_ON_ONCE(port_type >= node->seq_port_cnt))
629564
return 0;
630-
}
631-
632-
/* for PRP we should only forward frames from the slave ports
633-
* to the master port
634-
*/
635-
if (port->type != HSR_PT_MASTER)
636-
return 1;
637565

638566
spin_lock_bh(&node->seq_out_lock);
639567

@@ -643,11 +571,9 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
643571
goto out_new;
644572

645573
seq_bit = hsr_seq_block_bit(sequence_nr);
646-
if (__test_and_set_bit(seq_bit, block->seq_nrs))
574+
if (__test_and_set_bit(seq_bit, block->seq_nrs[port_type]))
647575
goto out_seen;
648576

649-
node->time_out[port->type] = jiffies;
650-
node->seq_out[port->type] = sequence_nr;
651577
out_new:
652578
spin_unlock_bh(&node->seq_out_lock);
653579
return 0;
@@ -656,6 +582,49 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
656582
spin_unlock_bh(&node->seq_out_lock);
657583
return 1;
658584
}
585+
586+
/* HSR duplicate discard: we check if the same frame has already been sent on
587+
* this outgoing interface. The check follows the general duplicate discard
588+
* algorithm.
589+
*
590+
* 'port' is the outgoing interface
591+
* 'frame' is the frame to be sent
592+
*
593+
* Return:
594+
* 1 if frame can be shown to have been sent recently on this interface,
595+
* 0 otherwise
596+
*/
597+
int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
598+
{
599+
return hsr_check_duplicate(frame, port->type - 1);
600+
}
601+
602+
/* PRP duplicate discard: we only consider frames that are received on port A
603+
* or port B and should go to the master port. For those, we check if they have
604+
* already been received by the host, i.e., master port. The check uses the
605+
* general duplicate discard algorithm, but without tracking multiple ports.
606+
*
607+
* 'port' is the outgoing interface
608+
* 'frame' is the frame to be sent
609+
*
610+
* Return:
611+
* 1 if frame can be shown to have been sent recently on this interface,
612+
* 0 otherwise
613+
*/
614+
int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
615+
{
616+
/* out-going frames are always in order */
617+
if (frame->port_rcv->type == HSR_PT_MASTER)
618+
return 0;
619+
620+
/* for PRP we should only forward frames from the slave ports
621+
* to the master port
622+
*/
623+
if (port->type != HSR_PT_MASTER)
624+
return 1;
625+
626+
return hsr_check_duplicate(frame, 0);
627+
}
659628
EXPORT_SYMBOL_IF_KUNIT(prp_register_frame_out);
660629

661630
static struct hsr_port *get_late_port(struct hsr_priv *hsr,
@@ -806,6 +775,39 @@ void *hsr_get_next_node(struct hsr_priv *hsr, void *_pos,
806775
return NULL;
807776
}
808777

778+
/* Fill the last sequence number that has been received from node on if1 by
779+
* finding the last sequence number sent on port B; accordingly get the last
780+
* received sequence number for if2 using sent sequence numbers on port A.
781+
*/
782+
static void fill_last_seq_nrs(struct hsr_node *node, u16 *if1_seq, u16 *if2_seq)
783+
{
784+
struct hsr_seq_block *block;
785+
unsigned int block_off;
786+
size_t block_sz;
787+
u16 seq_bit;
788+
789+
spin_lock_bh(&node->seq_out_lock);
790+
791+
/* Get last inserted block */
792+
block_off = (node->next_block - 1) & (HSR_MAX_SEQ_BLOCKS - 1);
793+
block_sz = hsr_seq_block_size(node);
794+
block = node->block_buf + block_off * block_sz;
795+
796+
if (!bitmap_empty(block->seq_nrs[HSR_PT_SLAVE_B - 1],
797+
HSR_SEQ_BLOCK_SIZE)) {
798+
seq_bit = find_last_bit(block->seq_nrs[HSR_PT_SLAVE_B - 1],
799+
HSR_SEQ_BLOCK_SIZE);
800+
*if1_seq = (block->block_idx << HSR_SEQ_BLOCK_SHIFT) | seq_bit;
801+
}
802+
if (!bitmap_empty(block->seq_nrs[HSR_PT_SLAVE_A - 1],
803+
HSR_SEQ_BLOCK_SIZE)) {
804+
seq_bit = find_last_bit(block->seq_nrs[HSR_PT_SLAVE_A - 1],
805+
HSR_SEQ_BLOCK_SIZE);
806+
*if2_seq = (block->block_idx << HSR_SEQ_BLOCK_SHIFT) | seq_bit;
807+
}
808+
spin_unlock_bh(&node->seq_out_lock);
809+
}
810+
809811
int hsr_get_node_data(struct hsr_priv *hsr,
810812
const unsigned char *addr,
811813
unsigned char addr_b[ETH_ALEN],
@@ -846,8 +848,10 @@ int hsr_get_node_data(struct hsr_priv *hsr,
846848
*if2_age = jiffies_to_msecs(tdiff);
847849

848850
/* Present sequence numbers as if they were incoming on interface */
849-
*if1_seq = node->seq_out[HSR_PT_SLAVE_B];
850-
*if2_seq = node->seq_out[HSR_PT_SLAVE_A];
851+
*if1_seq = 0;
852+
*if2_seq = 0;
853+
if (hsr->prot_version != PRP_V1)
854+
fill_last_seq_nrs(node, if1_seq, if2_seq);
851855

852856
if (node->addr_B_port != HSR_PT_NONE) {
853857
port = hsr_port_get_hsr(hsr, node->addr_B_port);

0 commit comments

Comments
 (0)