net: hsr: Fix PRP duplicate detection
authorJaakko Karrenpalo <jkarrenpalo@gmail.com>
Fri, 7 Mar 2025 16:16:59 +0000 (18:16 +0200)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 13 Mar 2025 09:04:22 +0000 (10:04 +0100)
Add PRP specific function for handling duplicate
packets. This is needed because of potential
L2 802.1p prioritization done by network switches.

The L2 prioritization can re-order the PRP packets
from a node causing the existing implementation to
discard the frame(s) that have been received 'late'
because the sequence number is before the previous
received packet. This can happen if the node is
sending multiple frames back-to-back with different
priority.

Signed-off-by: Jaakko Karrenpalo <jkarrenpalo@gmail.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20250307161700.1045-1-jkarrenpalo@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
net/hsr/hsr_device.c
net/hsr/hsr_forward.c
net/hsr/hsr_framereg.c
net/hsr/hsr_framereg.h
net/hsr/hsr_main.h

index c6f8614e9ed1fd207d3bddb672ce806e9c7392cb..439cfb7ad5d148ff7aee19f683c4282548f87858 100644 (file)
@@ -616,6 +616,7 @@ static struct hsr_proto_ops hsr_ops = {
        .drop_frame = hsr_drop_frame,
        .fill_frame_info = hsr_fill_frame_info,
        .invalid_dan_ingress_frame = hsr_invalid_dan_ingress_frame,
+       .register_frame_out = hsr_register_frame_out,
 };
 
 static struct hsr_proto_ops prp_ops = {
@@ -626,6 +627,7 @@ static struct hsr_proto_ops prp_ops = {
        .fill_frame_info = prp_fill_frame_info,
        .handle_san_frame = prp_handle_san_frame,
        .update_san_info = prp_update_san_info,
+       .register_frame_out = prp_register_frame_out,
 };
 
 void hsr_dev_setup(struct net_device *dev)
index a4bacf1985558ab62302a2a9d1d89d51f58f54d2..c67c0d35921de0a8fa8ee90e72641c2a73da14d0 100644 (file)
@@ -536,8 +536,8 @@ static void hsr_forward_do(struct hsr_frame_info *frame)
                 * Also for SAN, this shouldn't be done.
                 */
                if (!frame->is_from_san &&
-                   hsr_register_frame_out(port, frame->node_src,
-                                          frame->sequence_nr))
+                   hsr->proto_ops->register_frame_out &&
+                   hsr->proto_ops->register_frame_out(port, frame))
                        continue;
 
                if (frame->is_supervision && port->type == HSR_PT_MASTER &&
index 73bc6f659812f666d28f63dbc19012eb63afb18f..85991fab7db584842c8bde66c8ce68a2d9d00b46 100644 (file)
@@ -35,6 +35,7 @@ static bool seq_nr_after(u16 a, u16 b)
 
 #define seq_nr_before(a, b)            seq_nr_after((b), (a))
 #define seq_nr_before_or_eq(a, b)      (!seq_nr_after((a), (b)))
+#define PRP_DROP_WINDOW_LEN 32768
 
 bool hsr_addr_is_redbox(struct hsr_priv *hsr, unsigned char *addr)
 {
@@ -176,8 +177,11 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
                new_node->time_in[i] = now;
                new_node->time_out[i] = now;
        }
-       for (i = 0; i < HSR_PT_PORTS; i++)
+       for (i = 0; i < HSR_PT_PORTS; i++) {
                new_node->seq_out[i] = seq_out;
+               new_node->seq_expected[i] = seq_out + 1;
+               new_node->seq_start[i] = seq_out + 1;
+       }
 
        if (san && hsr->proto_ops->handle_san_frame)
                hsr->proto_ops->handle_san_frame(san, rx_port, new_node);
@@ -482,9 +486,11 @@ void hsr_register_frame_in(struct hsr_node *node, struct hsr_port *port,
  *      0 otherwise, or
  *      negative error code on error
  */
-int hsr_register_frame_out(struct hsr_port *port, struct hsr_node *node,
-                          u16 sequence_nr)
+int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
 {
+       struct hsr_node *node = frame->node_src;
+       u16 sequence_nr = frame->sequence_nr;
+
        spin_lock_bh(&node->seq_out_lock);
        if (seq_nr_before_or_eq(sequence_nr, node->seq_out[port->type]) &&
            time_is_after_jiffies(node->time_out[port->type] +
@@ -499,6 +505,89 @@ int hsr_register_frame_out(struct hsr_port *port, struct hsr_node *node,
        return 0;
 }
 
+/* Adaptation of the PRP duplicate discard algorithm described in wireshark
+ * wiki (https://wiki.wireshark.org/PRP)
+ *
+ * A drop window is maintained for both LANs with start sequence set to the
+ * first sequence accepted on the LAN that has not been seen on the other LAN,
+ * and expected sequence set to the latest received sequence number plus one.
+ *
+ * When a frame is received on either LAN it is compared against the received
+ * frames on the other LAN. If it is outside the drop window of the other LAN
+ * the frame is accepted and the drop window is updated.
+ * The drop window for the other LAN is reset.
+ *
+ * 'port' is the outgoing interface
+ * 'frame' is the frame to be sent
+ *
+ * Return:
+ *      1 if frame can be shown to have been sent recently on this interface,
+ *      0 otherwise
+ */
+int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
+{
+       enum hsr_port_type other_port;
+       enum hsr_port_type rcv_port;
+       struct hsr_node *node;
+       u16 sequence_diff;
+       u16 sequence_exp;
+       u16 sequence_nr;
+
+       /* out-going frames are always in order
+        * and can be checked the same way as for HSR
+        */
+       if (frame->port_rcv->type == HSR_PT_MASTER)
+               return hsr_register_frame_out(port, frame);
+
+       /* for PRP we should only forward frames from the slave ports
+        * to the master port
+        */
+       if (port->type != HSR_PT_MASTER)
+               return 1;
+
+       node = frame->node_src;
+       sequence_nr = frame->sequence_nr;
+       sequence_exp = sequence_nr + 1;
+       rcv_port = frame->port_rcv->type;
+       other_port = rcv_port == HSR_PT_SLAVE_A ? HSR_PT_SLAVE_B :
+                                HSR_PT_SLAVE_A;
+
+       spin_lock_bh(&node->seq_out_lock);
+       if (time_is_before_jiffies(node->time_out[port->type] +
+           msecs_to_jiffies(HSR_ENTRY_FORGET_TIME)) ||
+           (node->seq_start[rcv_port] == node->seq_expected[rcv_port] &&
+            node->seq_start[other_port] == node->seq_expected[other_port])) {
+               /* the node hasn't been sending for a while
+                * or both drop windows are empty, forward the frame
+                */
+               node->seq_start[rcv_port] = sequence_nr;
+       } else if (seq_nr_before(sequence_nr, node->seq_expected[other_port]) &&
+                  seq_nr_before_or_eq(node->seq_start[other_port], sequence_nr)) {
+               /* drop the frame, update the drop window for the other port
+                * and reset our drop window
+                */
+               node->seq_start[other_port] = sequence_exp;
+               node->seq_expected[rcv_port] = sequence_exp;
+               node->seq_start[rcv_port] = node->seq_expected[rcv_port];
+               spin_unlock_bh(&node->seq_out_lock);
+               return 1;
+       }
+
+       /* update the drop window for the port where this frame was received
+        * and clear the drop window for the other port
+        */
+       node->seq_start[other_port] = node->seq_expected[other_port];
+       node->seq_expected[rcv_port] = sequence_exp;
+       sequence_diff = sequence_exp - node->seq_start[rcv_port];
+       if (sequence_diff > PRP_DROP_WINDOW_LEN)
+               node->seq_start[rcv_port] = sequence_exp - PRP_DROP_WINDOW_LEN;
+
+       node->time_out[port->type] = jiffies;
+       node->seq_out[port->type] = sequence_nr;
+       spin_unlock_bh(&node->seq_out_lock);
+       return 0;
+}
+
 static struct hsr_port *get_late_port(struct hsr_priv *hsr,
                                      struct hsr_node *node)
 {
index 993fa950d81449b2a39f76ec00d0d3edf39ed1a2..b04948659d84d8c3a42343b1e8c52d26216a0b37 100644 (file)
@@ -44,8 +44,7 @@ void hsr_addr_subst_dest(struct hsr_node *node_src, struct sk_buff *skb,
 
 void hsr_register_frame_in(struct hsr_node *node, struct hsr_port *port,
                           u16 sequence_nr);
-int hsr_register_frame_out(struct hsr_port *port, struct hsr_node *node,
-                          u16 sequence_nr);
+int hsr_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame);
 
 void hsr_prune_nodes(struct timer_list *t);
 void hsr_prune_proxy_nodes(struct timer_list *t);
@@ -73,6 +72,8 @@ void prp_update_san_info(struct hsr_node *node, bool is_sup);
 bool hsr_is_node_in_db(struct list_head *node_db,
                       const unsigned char addr[ETH_ALEN]);
 
+int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame);
+
 struct hsr_node {
        struct list_head        mac_list;
        /* Protect R/W access to seq_out */
@@ -89,6 +90,9 @@ struct hsr_node {
        bool                    san_b;
        u16                     seq_out[HSR_PT_PORTS];
        bool                    removed;
+       /* PRP specific duplicate handling */
+       u16                     seq_expected[HSR_PT_PORTS];
+       u16                     seq_start[HSR_PT_PORTS];
        struct rcu_head         rcu_head;
 };
 
index 7561845b8bf6f43b14d85d92ae1c778581d4cc66..1bc47b17a2968c6af390c2b9dc7d993198122881 100644 (file)
@@ -175,6 +175,8 @@ struct hsr_proto_ops {
                               struct hsr_frame_info *frame);
        bool (*invalid_dan_ingress_frame)(__be16 protocol);
        void (*update_san_info)(struct hsr_node *node, bool is_sup);
+       int (*register_frame_out)(struct hsr_port *port,
+                                 struct hsr_frame_info *frame);
 };
 
 struct hsr_self_node {