* [PATCH net-next 1/4] net: hsr: add PRP interlink (RedBox) datapath and duplicate discard
2026-07-04 23:47 [PATCH net-next 0/4] net: hsr: PRP RedBox (PRP-SAN) support Xin Xie
@ 2026-07-04 23:47 ` Xin Xie
2026-07-04 23:47 ` [PATCH net-next 2/4] net: hsr: emit RedBox-MAC TLV in PRP RedBox supervision frames Xin Xie
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
A PRP RedBox proxies SANs that sit behind an interlink port: their frames
must reach the PRP network with the SAN source MAC preserved, and PRP
unicast must be steered between the LAN and the SAN segment correctly.
Add the PRP interlink forwarding rules to prp_drop_frame() and give RedBox
nodes a second duplicate-discard slot so the two LAN copies of a frame
destined to a SAN collapse to a single delivery out the interlink.
The destination classification (is the unicast DA a PRP-network node or a
proxied SAN) is resolved once per frame in fill_frame_info(), gated to PRP
RedBox devices, and cached in struct hsr_frame_info, so prp_drop_frame()
stays O(1) and does not walk the node tables for every candidate egress
port in the softIRQ path. HSR RedBox frame classification is untouched.
Factor the LAN A/B duplicate test into prp_is_lan_dup() so the new PRP
interlink rules do not change hsr_drop_frame() behaviour, including the
NETIF_F_HW_HSR_FWD path which keeps using the LAN-duplicate test only.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_forward.c | 49 ++++++++++++++++++++++++++++++++++++------
net/hsr/hsr_framereg.c | 10 ++++++---
net/hsr/hsr_framereg.h | 2 ++
3 files changed, 52 insertions(+), 9 deletions(-)
diff --git a/net/hsr/hsr_forward.c b/net/hsr/hsr_forward.c
index 0774981a6..efcd0acef 100644
--- a/net/hsr/hsr_forward.c
+++ b/net/hsr/hsr_forward.c
@@ -440,12 +440,37 @@ static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,
return dev_queue_xmit(skb);
}
+static bool prp_is_lan_dup(struct hsr_frame_info *frame,
+ struct hsr_port *port)
+{
+ enum hsr_port_type rx = frame->port_rcv->type;
+
+ return (rx == HSR_PT_SLAVE_A && port->type == HSR_PT_SLAVE_B) ||
+ (rx == HSR_PT_SLAVE_B && port->type == HSR_PT_SLAVE_A);
+}
+
bool prp_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
{
- return ((frame->port_rcv->type == HSR_PT_SLAVE_A &&
- port->type == HSR_PT_SLAVE_B) ||
- (frame->port_rcv->type == HSR_PT_SLAVE_B &&
- port->type == HSR_PT_SLAVE_A));
+ enum hsr_port_type rx = frame->port_rcv->type;
+
+ /* Supervision frames are not delivered to a SAN on the interlink. */
+ if (frame->is_supervision && port->type == HSR_PT_INTERLINK)
+ return true;
+
+ if (prp_is_lan_dup(frame, port))
+ return true;
+
+ /* LAN to interlink: keep PRP-network unicast off the SAN segment. */
+ if ((rx == HSR_PT_SLAVE_A || rx == HSR_PT_SLAVE_B) &&
+ port->type == HSR_PT_INTERLINK)
+ return frame->dst_in_node_db;
+
+ /* Interlink to LAN: keep SAN-to-SAN unicast local. */
+ if ((port->type == HSR_PT_SLAVE_A || port->type == HSR_PT_SLAVE_B) &&
+ rx == HSR_PT_INTERLINK)
+ return frame->dst_in_proxy_node_db;
+
+ return false;
}
bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
@@ -453,7 +478,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
struct sk_buff *skb;
if (port->dev->features & NETIF_F_HW_HSR_FWD)
- return prp_drop_frame(frame, port);
+ return prp_is_lan_dup(frame, port);
/* RedBox specific frames dropping policies
*
@@ -466,7 +491,7 @@ bool hsr_drop_frame(struct hsr_frame_info *frame, struct hsr_port *port)
* are addressed to interlink port (and are in the ProxyNodeTable).
*/
skb = frame->skb_hsr;
- if (skb && prp_drop_frame(frame, port) &&
+ if (skb && prp_is_lan_dup(frame, port) &&
is_unicast_ether_addr(eth_hdr(skb)->h_dest) &&
hsr_is_node_in_db(&port->hsr->proxy_node_db,
eth_hdr(skb)->h_dest)) {
@@ -706,6 +731,18 @@ static int fill_frame_info(struct hsr_frame_info *frame,
frame->is_vlan = false;
proto = ethhdr->h_proto;
+ /* PRP RedBox only: classify the unicast destination once so the
+ * per-egress-port decision in prp_drop_frame() stays O(1). HSR RedBox
+ * does its own classification and must not pay these node-table walks.
+ */
+ if (hsr->prot_version == PRP_V1 && hsr->redbox &&
+ is_unicast_ether_addr(ethhdr->h_dest)) {
+ frame->dst_in_node_db =
+ hsr_is_node_in_db(&hsr->node_db, ethhdr->h_dest);
+ frame->dst_in_proxy_node_db =
+ hsr_is_node_in_db(&hsr->proxy_node_db, ethhdr->h_dest);
+ }
+
if (proto == htons(ETH_P_8021Q))
frame->is_vlan = true;
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index e44929871..7a7c52a95 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -199,7 +199,7 @@ static struct hsr_node *hsr_add_node(struct hsr_priv *hsr,
spin_lock_init(&new_node->seq_out_lock);
if (hsr->prot_version == PRP_V1)
- new_node->seq_port_cnt = 1;
+ new_node->seq_port_cnt = hsr->redbox ? 2 : 1;
else
new_node->seq_port_cnt = HSR_PT_PORTS - 1;
@@ -649,9 +649,13 @@ int prp_register_frame_out(struct hsr_port *port, struct hsr_frame_info *frame)
if (frame->port_rcv->type == HSR_PT_MASTER)
return 0;
- /* for PRP we should only forward frames from the slave ports
- * to the master port
+ /* RedBox: forward LAN frames out the interlink to a SAN, deduping the
+ * two LAN copies on a dedicated slot.
*/
+ if (port->type == HSR_PT_INTERLINK)
+ return hsr_check_duplicate(frame, 1);
+
+ /* For PRP only slave-to-master frames are forwarded. */
if (port->type != HSR_PT_MASTER)
return 1;
diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h
index c65ecb925..127a3fb64 100644
--- a/net/hsr/hsr_framereg.h
+++ b/net/hsr/hsr_framereg.h
@@ -27,6 +27,8 @@ struct hsr_frame_info {
bool is_local_dest;
bool is_local_exclusive;
bool is_from_san;
+ bool dst_in_node_db;
+ bool dst_in_proxy_node_db;
};
void hsr_del_self_node(struct hsr_priv *hsr);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH net-next 2/4] net: hsr: emit RedBox-MAC TLV in PRP RedBox supervision frames
2026-07-04 23:47 [PATCH net-next 0/4] net: hsr: PRP RedBox (PRP-SAN) support Xin Xie
2026-07-04 23:47 ` [PATCH net-next 1/4] net: hsr: add PRP interlink (RedBox) datapath and duplicate discard Xin Xie
@ 2026-07-04 23:47 ` Xin Xie
2026-07-04 23:47 ` [PATCH net-next 3/4] net: hsr: allow PRP RedBox (interlink) creation Xin Xie
2026-07-04 23:47 ` [PATCH net-next 4/4] selftests: net: hsr: add PRP RedBox test Xin Xie
3 siblings, 0 replies; 5+ messages in thread
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
A PRP RedBox must announce the SANs it proxies so peers populate their
proxy node tables. The proxy-announce machinery (hsr_proxy_announce(),
armed via hsr->redbox) already iterates proxy_node_db under RCU and calls
send_sv_frame() once per SAN, but the PRP sender emitted neither the
announced SAN MAC nor the RedBox-MAC TLV that IEC 62439-3 requires.
Extend send_prp_supervision_frame() so that, for a proxy-announce
(identified by the interlink port, an O(1) test), the frame carries the
proxied SAN MAC as MacAddressA followed by the RedBox-MAC TLV (Type 30)
and an explicit End-of-TLV marker before padding.
hsr_get_node() must also accept the reinjected proxy-announce: a PRP
supervision frame is an untagged ETH_P_PRP frame (mac_len == ETH_HLEN, the
RCT is appended only on egress) sourced from macaddress_redbox, which is
never learned from data. Exempt only PRP supervision frames from the
hsr_ethhdr length guard; HSR (ETH_P_HSR) supervision is front-tagged and
keeps the original guard, so HSR malformed-frame filtering is unchanged.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_device.c | 33 ++++++++++++++++++++++++++++++---
net/hsr/hsr_framereg.c | 13 +++++++++++--
2 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index 5555b71ab..7cc21253c 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -372,10 +372,21 @@ static void send_prp_supervision_frame(struct hsr_port *master,
{
struct hsr_priv *hsr = master->hsr;
struct hsr_sup_payload *hsr_sp;
+ struct hsr_sup_tlv *hsr_stlv;
struct hsr_sup_tag *hsr_stag;
struct sk_buff *skb;
+ bool redbox_proxy;
+ int extra = 0;
+
+ redbox_proxy = hsr->redbox && master->type == HSR_PT_INTERLINK;
+
+ /* A proxy-announce carries a RedBox-MAC TLV and an EOT marker. */
+ if (redbox_proxy)
+ extra = sizeof(struct hsr_sup_tlv) +
+ sizeof(struct hsr_sup_payload) +
+ sizeof(struct hsr_sup_tlv);
- skb = hsr_init_skb(master, 0);
+ skb = hsr_init_skb(master, extra);
if (!skb) {
netdev_warn_once(master->dev, "PRP: Could not send supervision frame\n");
return;
@@ -393,9 +404,25 @@ static void send_prp_supervision_frame(struct hsr_port *master,
hsr_stag->tlv.HSR_TLV_type = PRP_TLV_LIFE_CHECK_DD;
hsr_stag->tlv.HSR_TLV_length = sizeof(struct hsr_sup_payload);
- /* Payload: MacAddressA */
+ /* Payload: MacAddressA, the announced node. */
hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
- ether_addr_copy(hsr_sp->macaddress_A, master->dev->dev_addr);
+ ether_addr_copy(hsr_sp->macaddress_A, addr);
+
+ /* Proxy-announce: append the RedBox-MAC TLV (Type 30) and an explicit
+ * EOT to terminate the TLV chain before zero padding.
+ */
+ if (redbox_proxy) {
+ hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv));
+ hsr_stlv->HSR_TLV_type = PRP_TLV_REDBOX_MAC;
+ hsr_stlv->HSR_TLV_length = sizeof(struct hsr_sup_payload);
+
+ hsr_sp = skb_put(skb, sizeof(struct hsr_sup_payload));
+ ether_addr_copy(hsr_sp->macaddress_A, hsr->macaddress_redbox);
+
+ hsr_stlv = skb_put(skb, sizeof(struct hsr_sup_tlv));
+ hsr_stlv->HSR_TLV_type = HSR_TLV_EOT;
+ hsr_stlv->HSR_TLV_length = 0;
+ }
if (skb_put_padto(skb, ETH_ZLEN)) {
spin_unlock_bh(&hsr->seqnr_lock);
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
index 7a7c52a95..37ade2dde 100644
--- a/net/hsr/hsr_framereg.c
+++ b/net/hsr/hsr_framereg.c
@@ -293,8 +293,17 @@ struct hsr_node *hsr_get_node(struct hsr_port *port, struct list_head *node_db,
*/
if (ethhdr->h_proto == htons(ETH_P_PRP) ||
ethhdr->h_proto == htons(ETH_P_HSR)) {
- /* Check if skb contains hsr_ethhdr */
- if (skb->mac_len < sizeof(struct hsr_ethhdr))
+ bool prp_sup;
+
+ /* A PRP supervision frame is an untagged ETH_P_PRP frame
+ * (mac_len == ETH_HLEN); its RCT is appended only on egress.
+ * HSR (ETH_P_HSR) supervision is front-tagged and still must
+ * contain a struct hsr_ethhdr.
+ */
+ prp_sup = hsr->prot_version == PRP_V1 &&
+ ethhdr->h_proto == htons(ETH_P_PRP) && is_sup;
+
+ if (!prp_sup && skb->mac_len < sizeof(struct hsr_ethhdr))
return NULL;
} else {
rct = skb_get_PRP_rct(skb);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH net-next 3/4] net: hsr: allow PRP RedBox (interlink) creation
2026-07-04 23:47 [PATCH net-next 0/4] net: hsr: PRP RedBox (PRP-SAN) support Xin Xie
2026-07-04 23:47 ` [PATCH net-next 1/4] net: hsr: add PRP interlink (RedBox) datapath and duplicate discard Xin Xie
2026-07-04 23:47 ` [PATCH net-next 2/4] net: hsr: emit RedBox-MAC TLV in PRP RedBox supervision frames Xin Xie
@ 2026-07-04 23:47 ` Xin Xie
2026-07-04 23:47 ` [PATCH net-next 4/4] selftests: net: hsr: add PRP RedBox test Xin Xie
3 siblings, 0 replies; 5+ messages in thread
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
With the PRP interlink datapath, duplicate discard and supervision support
in place, a PRP device can act as a RedBox. Remove the rtnetlink rejection
of "type hsr ... interlink <dev> proto 1"; the feature is implemented
unconditionally by the preceding patches.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
net/hsr/hsr_netlink.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
index 8099f2069..88940e801 100644
--- a/net/hsr/hsr_netlink.c
+++ b/net/hsr/hsr_netlink.c
@@ -121,14 +121,8 @@ static int hsr_newlink(struct net_device *dev,
}
}
- if (proto == HSR_PROTOCOL_PRP) {
+ if (proto == HSR_PROTOCOL_PRP)
proto_version = PRP_V1;
- if (interlink) {
- NL_SET_ERR_MSG_MOD(extack,
- "Interlink only works with HSR");
- return -EINVAL;
- }
- }
return hsr_dev_finalize(dev, link, interlink, multicast_spec,
proto_version, extack);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH net-next 4/4] selftests: net: hsr: add PRP RedBox test
2026-07-04 23:47 [PATCH net-next 0/4] net: hsr: PRP RedBox (PRP-SAN) support Xin Xie
` (2 preceding siblings ...)
2026-07-04 23:47 ` [PATCH net-next 3/4] net: hsr: allow PRP RedBox (interlink) creation Xin Xie
@ 2026-07-04 23:47 ` Xin Xie
3 siblings, 0 replies; 5+ messages in thread
From: Xin Xie @ 2026-07-04 23:47 UTC (permalink / raw)
To: netdev
Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Shuah Khan, linux-kselftest, linux-kernel, Xin Xie
Add a kselftest that builds a PRP RedBox (interlink) with a SAN behind the
interlink and a peer DANP, and checks bidirectional unicast across the
interlink, preservation of the SAN source MAC on the PRP network, and that
the proxy-announce supervision frame carries the RedBox-MAC TLV (Type 30)
terminated by an EOT marker. It reuses the hsr_common.sh / lib.sh helpers
and skips cleanly on a kernel or iproute2 without PRP interlink support.
Signed-off-by: Xin Xie <xiexinet@gmail.com>
---
tools/testing/selftests/net/hsr/Makefile | 1 +
.../selftests/net/hsr/hsr_prp_redbox.sh | 96 +++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100755 tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
diff --git a/tools/testing/selftests/net/hsr/Makefile b/tools/testing/selftests/net/hsr/Makefile
index 31fb9326c..2150e487a 100644
--- a/tools/testing/selftests/net/hsr/Makefile
+++ b/tools/testing/selftests/net/hsr/Makefile
@@ -4,6 +4,7 @@ top_srcdir = ../../../../..
TEST_PROGS := \
hsr_ping.sh \
+ hsr_prp_redbox.sh \
hsr_redbox.sh \
link_faults.sh \
prp_ping.sh \
diff --git a/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
new file mode 100755
index 000000000..8ae36737b
--- /dev/null
+++ b/tools/testing/selftests/net/hsr/hsr_prp_redbox.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test a PRP RedBox (PRP-SAN): a SAN that sits behind the interlink port must
+# reach, and be reached by, a peer DANP on the PRP network with its own MAC
+# preserved on the wire, and the RedBox must announce the SAN with a RedBox-MAC
+# TLV (terminated by an EOT marker) in its PRP supervision frames.
+#
+# RB PRP RedBox: prp0 over rb_a/rb_b (LAN A/B) + interlink rb_il
+# PEER peer DANP : prp0 over pe_a/pe_b, 100.64.0.2
+# SAN SAN : san_il, own MAC, 100.64.0.51 (behind the interlink)
+
+ipv6=false
+
+source ./hsr_common.sh
+
+check_prerequisites
+
+if ! command -v tcpdump >/dev/null 2>&1; then
+ echo "SKIP: This test requires tcpdump"
+ exit $ksft_skip
+fi
+
+if ! ip link help hsr 2>&1 | grep -q interlink; then
+ echo "SKIP: iproute2 too old (no hsr interlink support)"
+ exit $ksft_skip
+fi
+
+setup_ns RB PEER SAN
+trap 'cleanup_ns "$RB" "$PEER" "$SAN"' EXIT
+
+ip link add rb_a netns "$RB" type veth peer name pe_a netns "$PEER"
+ip link add rb_b netns "$RB" type veth peer name pe_b netns "$PEER"
+ip link add rb_il netns "$RB" type veth peer name san_il netns "$SAN"
+
+ip -n "$RB" link set rb_a up
+ip -n "$RB" link set rb_b up
+ip -n "$RB" link set rb_il up
+ip -n "$PEER" link set pe_a up
+ip -n "$PEER" link set pe_b up
+ip -n "$SAN" link set san_il up
+ip -n "$SAN" addr add 100.64.0.51/24 dev san_il
+
+# Feature gate: PRP interlink (RedBox) creation. A kernel without PRP RedBox
+# support rejects this with -EINVAL, so SKIP rather than FAIL.
+if ! ip -n "$RB" link add name prp0 type hsr slave1 rb_a slave2 rb_b \
+ interlink rb_il proto 1 2>/dev/null; then
+ echo "SKIP: kernel without PRP RedBox (interlink) support"
+ exit $ksft_skip
+fi
+ip -n "$RB" link set prp0 up
+ip -n "$PEER" link add name prp0 type hsr slave1 pe_a slave2 pe_b proto 1
+ip -n "$PEER" link set prp0 up
+ip -n "$PEER" addr add 100.64.0.2/24 dev prp0
+sleep 1
+
+san_mac=$(ip -n "$SAN" -br link show san_il | awk '{print $3}')
+rb_mac=$(ip -n "$RB" -br link show rb_il | awk '{print $3}')
+
+# Bidirectional unicast across the interlink.
+do_ping "$PEER" 100.64.0.51
+do_ping "$SAN" 100.64.0.2
+stop_if_error "PRP RedBox bidirectional unicast failed"
+
+# The SAN source MAC must be preserved on the PRP network, not laundered to the
+# RedBox MAC: the peer resolves the SAN IP to the SAN's own MAC.
+neigh=$(ip -n "$PEER" neigh show 100.64.0.51 | awk '{print $5}')
+if [ "$neigh" != "$san_mac" ]; then
+ echo "SAN MAC preservation [ FAIL ]: peer resolved 100.64.0.51 to" \
+ "'$neigh', expected $san_mac" 1>&2
+ ret=1
+fi
+stop_if_error "SAN MAC not preserved on the PRP network"
+
+# The proxy-announce supervision frame must carry, in order, the life-check TLV
+# (type 0x14, len 6) + MacAddressA == SAN MAC + the RedBox-MAC TLV (type 0x1e,
+# len 6) + MacAddressRedBox == RedBox MAC + the EOT marker (0x0000).
+( ip netns exec "$SAN" ping -i 0.2 -q 100.64.0.2 >/dev/null 2>&1 & )
+cap=$(ip netns exec "$PEER" timeout 5 tcpdump -i pe_a -nn -x \
+ "ether proto 0x88fb and ether src $rb_mac" 2>/dev/null || true)
+ip netns exec "$SAN" pkill -f "ping -i 0.2" 2>/dev/null || true
+
+san_hex=$(echo "$san_mac" | tr -d ':')
+rb_hex=$(echo "$rb_mac" | tr -d ':')
+# Reassemble contiguous frame hex: drop the "0x0010:" offset labels and spaces.
+frame_hex=$(echo "$cap" | awk '/^[[:space:]]*0x[0-9a-f]+:/ {
+ sub(/^[[:space:]]*0x[0-9a-f]+:[[:space:]]*/, ""); gsub(/ /, ""); printf "%s", $0 }')
+if ! echo "$frame_hex" | grep -q "1406${san_hex}1e06${rb_hex}0000"; then
+ echo "supervision RedBox-MAC TLV [ FAIL ]: missing SAN MAC, Type-30" \
+ "payload, or EOT" 1>&2
+ ret=1
+fi
+stop_if_error "PRP RedBox supervision RedBox-MAC TLV/EOT check failed"
+
+echo "INFO: PRP RedBox (PRP-SAN) conformance checks passed"
+exit $ret
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread