From nobody Sat Jul 25 05:31:26 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0EE4D3E3C5A; Fri, 17 Jul 2026 09:55:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282122; cv=none; b=DqK3vRJLlnkgBN/DopT9DOtp5kVK8kiEfrhGeOHYE6PITdTZ096vjWoBlW0CbH2CD2jnQAqZtVit2fWqOGrwxERYPWo83eJCOQa8Q+kC+9KUo0u9kwb0dmnTQze1MGt2uvD8sDrZ6b2GfBIQDjcO0XSMatBUViasF2TLIoVIhbI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282122; c=relaxed/simple; bh=DDeODc45ZxenPhxOiIhZ5Kw72EyW//h/t+rD62y0OqE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=XtbYDtgEpgT3H9gCb34xsFiR1JGmoafF78Rq2nVhziSJdC9yzMx7wkfRAZj6UqEGg8mksJUNVXlcZGIlU0qTDwocZwVWQnVocec3Z8VF4ZYcwFFO7oKUXr9JhVuWUlX9j/BYfocxDmANpEZ4PUj4JfqsqM6Y8ThB8qAppXex2MU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 720C81A0206; Fri, 17 Jul 2026 11:55:19 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 3A2391A01CE; Fri, 17 Jul 2026 11:55:19 +0200 (CEST) Received: from localhost.localdomain (mega.ap.freescale.net [10.192.208.232]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id F0E3618000B5; Fri, 17 Jul 2026 17:55:16 +0800 (+08) From: Xiaoliang Yang To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, jhs@mojatatu.com, jiri@resnulli.us, horms@kernel.org, shuah@kernel.org, vladimir.oltean@nxp.com, vinicius.gomes@intel.com, fejes@inf.elte.hu, xiaoliang.yang_1@nxp.com Subject: [PATCH net-next 1/6] uapi: if_ether: add ETH_P_RTAG for IEEE 802.1CB R-TAG Date: Fri, 17 Jul 2026 17:55:44 +0800 Message-Id: <20260717095549.10565-2-xiaoliang.yang_1@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> References: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" The IEEE 802.1CB-2017 standard defines the Redundancy Tag (R-TAG) with EtherType 0xF1C1. Add ETH_P_RTAG to the kernel's EtherType definitions so that it can be used by tc classifiers (e.g. cls_flower) and the FRER tc action for stream identification on the ingress path. Signed-off-by: Xiaoliang Yang --- include/uapi/linux/if_ether.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index fb5efc8e06cc..2d909078cde1 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -122,6 +122,7 @@ #define ETH_P_DSA_8021Q 0xDADB /* Fake VLAN Header for DSA [ NOT AN OFFIC= IALLY REGISTERED ID ] */ #define ETH_P_DSA_A5PSW 0xE001 /* A5PSW Tag Value [ NOT AN OFFICIALLY REG= ISTERED ID ] */ #define ETH_P_IFE 0xED3E /* ForCES inter-FE LFB type */ +#define ETH_P_RTAG 0xF1C1 /* Redundancy Tag (IEEE 802.1CB) */ #define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTE= RED ID ] */ #define ETH_P_NXP_NETC 0xFD3A /* NXP NETC DSA [ NOT AN OFFICIALLY REGIST= ERED ID ] */ =20 --=20 2.17.1 From nobody Sat Jul 25 05:31:26 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DB9D33E7BBC; Fri, 17 Jul 2026 09:55:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282124; cv=none; b=hoKDtLeXKHKTKDa6yeRP0YiAe7JNMf3T8Au/Hz+0OpbfqGY+cP5DJq4Oa3lkDGb/e97p8D/qXVJL6Wnn+F37INYthf8KFVWvz1nFIJ0UGAnBgyIFSvDtgOK290YDydmOAgT7UyVwMCzBlw67NHacToV2s94D2HbO1mAYP+PgwWA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282124; c=relaxed/simple; bh=jJ4dPiOt0VnW+1/y5QpkX2tVW+trXodIRZaAJVuBLUk=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=BZMg72PbfXlqjZ0I965w9m4VrVT7I4SJn70zxQbA1sHc9YaQL0ssjWNWpfpjLD3r2cdvxiKaFviRPymylYwS4A4vvyaG731mDA0jtMBYYKuXkyBkYudMCvwGpxWBm2fK3x2eEWu66d0qT/fdEHELtDfmNj00S1bMqZicS2U55Oc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 4C0D0200205; Fri, 17 Jul 2026 11:55:21 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 155A02001EB; Fri, 17 Jul 2026 11:55:21 +0200 (CEST) Received: from localhost.localdomain (mega.ap.freescale.net [10.192.208.232]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id CBF2E1800229; Fri, 17 Jul 2026 17:55:18 +0800 (+08) From: Xiaoliang Yang To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, jhs@mojatatu.com, jiri@resnulli.us, horms@kernel.org, shuah@kernel.org, vladimir.oltean@nxp.com, vinicius.gomes@intel.com, fejes@inf.elte.hu, xiaoliang.yang_1@nxp.com Subject: [PATCH net-next 2/6] uapi: pkt_cls: add TCA_ID_FRER action identifier Date: Fri, 17 Jul 2026 17:55:45 +0800 Message-Id: <20260717095549.10565-3-xiaoliang.yang_1@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> References: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Register TCA_ID_FRER in the global tc action ID enum so that the FRER tc action can be identified uniquely among all tc actions. Signed-off-by: Xiaoliang Yang --- include/uapi/linux/pkt_cls.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h index 28d94b11d1aa..9b87f0455110 100644 --- a/include/uapi/linux/pkt_cls.h +++ b/include/uapi/linux/pkt_cls.h @@ -139,6 +139,7 @@ enum tca_id { TCA_ID_MPLS, TCA_ID_CT, TCA_ID_GATE, + TCA_ID_FRER, /* other actions go here */ __TCA_ID_MAX =3D 255 }; --=20 2.17.1 From nobody Sat Jul 25 05:31:26 2026 Received: from inva020.nxp.com (inva020.nxp.com [92.121.34.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E88743E8C55; Fri, 17 Jul 2026 09:55:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282126; cv=none; b=YoUTJW48W27St1d9juN6bBrX3Xy8EFqVaKijsCuX+SO3HMMOrWstgj5a+4qfFVXKUAfX/hYIXeMoACHORnw62QaXiE1HwlhOHWZyWJwNwHTI5X9++9OD2ani3iaoAd6XMYG31UVhhq3CA/W7Jr+j7/6nLSa78njMiGlLNzn38rU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282126; c=relaxed/simple; bh=jsbsYpLrNq4V7qq+WOyrPMxgrzakuow/6sMDxaTx2ws=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=e6WDaUh7bzlLNU89vrLaAZmDXiFTo5u9fxGWN4v/CmsOy6RtcKZ8G11bkEaR9K0N4fXkhloRVFput0jBawMXOaJdA2m9H6hAzyKjdKd+uBXEtpAXaFIl8p5hWurfJDtlbarHq+KrGCJgJGb85C1OohJIG1o16hx0esxeH7JJXKY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva020.nxp.com (localhost [127.0.0.1]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id 321FF1A021C; Fri, 17 Jul 2026 11:55:23 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva020.eu-rdc02.nxp.com (Postfix) with ESMTP id E94CC1A0212; Fri, 17 Jul 2026 11:55:22 +0200 (CEST) Received: from localhost.localdomain (mega.ap.freescale.net [10.192.208.232]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id AE99018000B0; Fri, 17 Jul 2026 17:55:20 +0800 (+08) From: Xiaoliang Yang To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, jhs@mojatatu.com, jiri@resnulli.us, horms@kernel.org, shuah@kernel.org, vladimir.oltean@nxp.com, vinicius.gomes@intel.com, fejes@inf.elte.hu, xiaoliang.yang_1@nxp.com Subject: [PATCH net-next 3/6] uapi: tc_act: add tc_frer UAPI header Date: Fri, 17 Jul 2026 17:55:46 +0800 Message-Id: <20260717095549.10565-4-xiaoliang.yang_1@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> References: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Define the netlink attribute layout and enumerations for the FRER tc action (IEEE 802.1CB Frame Replication and Elimination for Reliability). The action is split into two functional sub-commands selected by the TCA_FRER_FUNC attribute: TCA_FRER_FUNC_PUSH - Egress: sequence number generation and R-TAG insertion. The action inserts an R-TAG with the current sequence number into the frame before passing it on. When chained with "action mirred egress mirror", the mirrored copy already carries the R-TAG, so all replicated frames on different egress paths carry the same sequence number without any additional shared state. TCA_FRER_FUNC_RECOVER - Ingress: duplicate detection and elimination. Multiple ingress filters can share the same recovery state by referencing the same action index, implementing Sequence Recovery across ports (IEEE 802.1CB Section 7.4.2). When TCA_FRER_RCVY_INDIVIDUAL flag is set, the action uses private per-action state (Individual Recovery, Section 7.5). Statistics attributes map directly to the managed objects defined in IEEE 802.1CB Table 10-1. Signed-off-by: Xiaoliang Yang --- include/uapi/linux/tc_act/tc_frer.h | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 include/uapi/linux/tc_act/tc_frer.h diff --git a/include/uapi/linux/tc_act/tc_frer.h b/include/uapi/linux/tc_ac= t/tc_frer.h new file mode 100644 index 000000000000..241e90827e26 --- /dev/null +++ b/include/uapi/linux/tc_act/tc_frer.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* Copyright 2026 NXP */ + +#ifndef __LINUX_TC_FRER_H +#define __LINUX_TC_FRER_H + +#include + +/* Base parameters passed in TCA_FRER_PARMS */ +struct tc_frer { + tc_gen; +}; + +/** + * enum TCA_FRER_* - netlink attributes for the FRER tc action + * + * @TCA_FRER_FUNC: Functional sub-command (tc_frer_func). + * Mandatory. + * @TCA_FRER_TAG_TYPE: Redundancy tag type (tc_frer_tag_type). + * Mandatory. + * + * Push-specific attributes (TCA_FRER_FUNC_PUSH): + * Recover-specific attributes (TCA_FRER_FUNC_RECOVER): + * @TCA_FRER_RCVY_INDIVIDUAL: Flag. Force Individual Recovery. + * @TCA_FRER_RCVY_ALG: u8. Recovery algorithm (tc_frer_rcvy_alg). + * @TCA_FRER_RCVY_HISTORY_LEN: u8. SequenceHistory window size (1-32). + * Maps to frerSeqRcvyHistoryLength. + * @TCA_FRER_RCVY_RESET_MSEC: u32. Reset timer in milliseconds. + * 0 disables the timer. + * Maps to frerSeqRcvyResetMSec. + * @TCA_FRER_RCVY_TAKE_NO_SEQ: Flag. Accept frames without a redundancy + * tag and pass them unconditionally. + * Maps to frerSeqRcvyTakeNoSeq. + * @TCA_FRER_RCVY_TAG_POP: Flag. Remove the redundancy tag from + * frames that pass the recovery function. + * + * Read-only statistics (filled on dump, IEEE 802.1CB Table 10-1): + * @TCA_FRER_STATS_TAGLESS_PKTS: frerCpsSeqRcvyTaglessPackets + * @TCA_FRER_STATS_OUT_OF_ORDER_PKTS: frerCpsSeqRcvyOutOfOrderPackets + * @TCA_FRER_STATS_ROGUE_PKTS: frerCpsSeqRcvyRoguePackets + * @TCA_FRER_STATS_LOST_PKTS: frerCpsSeqRcvyLostPackets + * @TCA_FRER_STATS_RESETS: frerCpsSeqRcvyResets + * @TCA_FRER_STATS_PASSED_PKTS: frerCpsSeqRcvyPassedPackets + * @TCA_FRER_STATS_DISCARDED_PKTS: frerCpsSeqRcvyDiscardedPackets + * @TCA_FRER_STATS_SEQGEN_PKTS: frerCpsSeqGenPackets + */ +enum { + TCA_FRER_UNSPEC, + TCA_FRER_TM, /* struct tcf_t */ + TCA_FRER_PARMS, /* struct tc_frer */ + TCA_FRER_PAD, + TCA_FRER_FUNC, /* u8: tc_frer_func */ + TCA_FRER_TAG_TYPE, /* u8: tc_frer_tag_type */ + TCA_FRER_RCVY_INDIVIDUAL, /* NLA_FLAG */ + TCA_FRER_RCVY_ALG, /* u8: tc_frer_rcvy_alg */ + TCA_FRER_RCVY_HISTORY_LEN, /* u8: 1-32 */ + TCA_FRER_RCVY_RESET_MSEC, /* u32 */ + TCA_FRER_RCVY_TAKE_NO_SEQ, /* NLA_FLAG */ + TCA_FRER_RCVY_TAG_POP, /* NLA_FLAG */ + TCA_FRER_STATS_TAGLESS_PKTS, /* u64 */ + TCA_FRER_STATS_OUT_OF_ORDER_PKTS, /* u64 */ + TCA_FRER_STATS_ROGUE_PKTS, /* u64 */ + TCA_FRER_STATS_LOST_PKTS, /* u64 */ + TCA_FRER_STATS_RESETS, /* u64 */ + TCA_FRER_STATS_PASSED_PKTS, /* u64 */ + TCA_FRER_STATS_DISCARDED_PKTS, /* u64 */ + TCA_FRER_STATS_SEQGEN_PKTS, /* u64 */ + __TCA_FRER_MAX, +}; + +#define TCA_FRER_MAX (__TCA_FRER_MAX - 1) + +enum tc_frer_func { + TCA_FRER_FUNC_PUSH =3D 1, + TCA_FRER_FUNC_RECOVER =3D 2, +}; + +enum tc_frer_tag_type { + TCA_FRER_TAG_RTAG =3D 1, + TCA_FRER_TAG_HSR, + TCA_FRER_TAG_PRP, +}; + +enum tc_frer_rcvy_alg { + TCA_FRER_RCVY_VECTOR_ALG =3D 0, /* IEEE 802.1CB 7.4.3.4 */ + TCA_FRER_RCVY_MATCH_ALG =3D 1, /* IEEE 802.1CB 7.4.3.5 */ +}; + +#endif /* __LINUX_TC_FRER_H */ --=20 2.17.1 From nobody Sat Jul 25 05:31:26 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E5D513E6DCE; Fri, 17 Jul 2026 09:55:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282130; cv=none; b=P3JQ6TXMCvOqaqlgAZnzl/i6kYC1WxXa5dHPKGgt8M8ZrPDLlyd+B4jyV1jDqBHNKzp8ElDJcarPSlhB5Ec20iUS1cIDM+GGqzMUw7nxUeJpCMuKlEJhTrUgoC982bHKVDjcLJ5iUVFlYqhdwzLFeX/mhYnunsZTJbS8tPqWFmA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282130; c=relaxed/simple; bh=WnsK+pdKKPRHOvMYgicUXPfzOzXiZYVPa2xaoFdSpCM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=n2EQNdj/Exl1tyZS2VBnP0Ge58pUcc660KSvOwx3BfcK6ZX+E7qqr/Kl1olRmXPR0KsLzs8hLb+QQbcSeB5sR64YLK47QfOGnk2JyXy8LVMvid1yqT8Mt4szDAOVXbPCYrp/8U7I7b4E4WtuspWMyzPws8Q+NZLmYDnL5klP16Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 43F8E2001F8; Fri, 17 Jul 2026 11:55:25 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id D827F200201; Fri, 17 Jul 2026 11:55:24 +0200 (CEST) Received: from localhost.localdomain (mega.ap.freescale.net [10.192.208.232]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 86E401800226; Fri, 17 Jul 2026 17:55:22 +0800 (+08) From: Xiaoliang Yang To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, jhs@mojatatu.com, jiri@resnulli.us, horms@kernel.org, shuah@kernel.org, vladimir.oltean@nxp.com, vinicius.gomes@intel.com, fejes@inf.elte.hu, xiaoliang.yang_1@nxp.com Subject: [PATCH net-next 4/6] net: sched: act_frer: add FRER tc action Date: Fri, 17 Jul 2026 17:55:47 +0800 Message-Id: <20260717095549.10565-5-xiaoliang.yang_1@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> References: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Introduce the FRER tc action for IEEE 802.1CB. This patch adds the module skeleton, the shared sequence-generator infrastructure, the TCA_FRER_FUNC_PUSH data path, and the TCA_FRER_FUNC_RECOVER data path. Sequence generation (IEEE 802.1CB Section 7.4.1): Each push action embeds a struct frer_seqgen directly in tcf_frer, protected by a per-action spinlock. The sequence counter wraps at 65536 (16-bit R-TAG field). When a Talker chains "action frer push" with "action mirred egress mirror", both the primary and the mirrored frame carry the same R-TAG because mirred copies the already-modified skb. No changes to act_mirred are required (Split function, Section 7.7). Sequence Recovery vs. Individual Recovery (IEEE 802.1CB Section 7.5): Sequence Recovery (cross-port deduplication): Multiple ingress filters on different ports share one recover action by referencing the same action index. They all operate on the same struct frer_rcvy embedded in that tcf_frer instance and protected by a spinlock. A frame arriving on any port is checked against the shared sequence history; the first copy passes and all later copies with the same sequence number are discarded. Individual Recovery (per-port independent deduplication): Each action uses its own frer_rcvy embedded directly in tcf_frer. Selected when the user sets the "individual" flag. Recovery algorithms: Vector (7.4.3.4, default): 32-bit history bit-vector, handles out-of-order delivery within the window. Match (7.4.3.5): remembers only the last accepted sequence number. Reset timer: An hrtimer fires after frerSeqRcvyResetMSec ms of inactivity. CLOCK_MONOTONIC is used throughout. The reset runs in a workqueue to avoid holding the spinlock in the hrtimer callback. R-TAG wire format (IEEE 802.1CB 7.8, EtherType 0xF1C1): [Dst MAC 6B][Src MAC 6B][Optional 802.1Q tag 4B][0xF1C1 2B] [Reserved 2B][Sequence Number 2B][Encapsulated EtherType 2B][Payload] Signed-off-by: Xiaoliang Yang --- include/net/flow_offload.h | 11 + include/net/tc_act/tc_frer.h | 71 +++ net/sched/Kconfig | 16 + net/sched/Makefile | 1 + net/sched/act_frer.c | 835 +++++++++++++++++++++++++++++++++++ 5 files changed, 934 insertions(+) create mode 100644 include/net/tc_act/tc_frer.h create mode 100644 net/sched/act_frer.c diff --git a/include/net/flow_offload.h b/include/net/flow_offload.h index 70a02ee14308..8d97a5f293e6 100644 --- a/include/net/flow_offload.h +++ b/include/net/flow_offload.h @@ -184,6 +184,7 @@ enum flow_action_id { FLOW_ACTION_VLAN_PUSH_ETH, FLOW_ACTION_VLAN_POP_ETH, FLOW_ACTION_CONTINUE, + FLOW_ACTION_FRER, NUM_FLOW_ACTIONS, }; =20 @@ -329,6 +330,16 @@ struct flow_action_entry { struct { /* FLOW_ACTION_PPPOE_PUSH */ u16 sid; } pppoe; + struct { /* FLOW_ACTION_FRER */ + u8 func; + u8 tag_type; + bool individual; + u8 rcvy_alg; + u8 rcvy_history_len; + u32 rcvy_reset_msec; + bool tag_pop; + bool take_no_seq; + } frer; }; struct flow_action_cookie *user_cookie; /* user defined action cookie */ }; diff --git a/include/net/tc_act/tc_frer.h b/include/net/tc_act/tc_frer.h new file mode 100644 index 000000000000..5f6f8ca70813 --- /dev/null +++ b/include/net/tc_act/tc_frer.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright 2026 NXP */ + +#ifndef __NET_TC_FRER_H +#define __NET_TC_FRER_H + +#include +#include + +/** + * struct frer_seqgen - sequence number generator state (embedded in tcf_f= rer) + */ +struct frer_seqgen { + u32 gen_seq_num; + u64 seq_space; /* 1 << 16 */ + spinlock_t lock; /* protects frer_seqgen state */ + u64 stats_pkts; /* frerCpsSeqGenPackets */ +}; + +/** + * struct frer_rcvy - sequence recovery state (embedded in tcf_frer) + * + */ +struct frer_rcvy { + u8 alg; + u8 history_len; /* 1-32 */ + u32 reset_msec; + u64 seq_space; + u32 rcvy_seq_num; + u32 seq_history; + bool take_any; + bool take_no_seq; + struct hrtimer hrtimer; + spinlock_t lock; /* protects frer_rcvy state */ + /* statistics */ + u64 stats_tagless_pkts; + u64 stats_out_of_order_pkts; + u64 stats_rogue_pkts; + u64 stats_lost_pkts; + u64 stats_resets; + u64 stats_passed_pkts; + u64 stats_discarded_pkts; +}; + +/** + * struct tcf_frer - per tc_action FRER private data + */ +struct tcf_frer { + struct tc_action common; + u8 func; + u8 tag_type; + bool tag_pop; + bool individual; /* Individual Recovery flag */ + /* push path */ + struct frer_seqgen seqgen; + /* recover path */ + struct frer_rcvy rcvy; +}; + +#define to_frer(a) ((struct tcf_frer *)(a)) + +static inline bool is_tcf_frer(const struct tc_action *a) +{ +#ifdef CONFIG_NET_CLS_ACT + if (a->ops && a->ops->id =3D=3D TCA_ID_FRER) + return true; +#endif + return false; +} + +#endif /* __NET_TC_FRER_H */ diff --git a/net/sched/Kconfig b/net/sched/Kconfig index 6ddff028b81a..7ca79b3eb5b3 100644 --- a/net/sched/Kconfig +++ b/net/sched/Kconfig @@ -939,6 +939,22 @@ config NET_ACT_GATE To compile this code as a module, choose M here: the module will be called act_gate. =20 +config NET_ACT_FRER + tristate "IEEE 802.1CB FRER tc action" + depends on NET_CLS_ACT + help + Say Y here to enable the IEEE 802.1CB FRER tc action. The action + implements the Sequence Generation Function (egress R-TAG insertion + with shared per-stream sequence counter) and the Sequence Recovery + Function (ingress duplicate detection and elimination) described in + IEEE 802.1CB-2017. + + Both Sequence Recovery (cross-port shared state via rcvy-id) and + Individual Recovery (per-port independent state) are supported. + + To compile this code as a module, choose M here: the + module will be called act_frer. + config NET_IFE_SKBMARK tristate "Support to encoding decoding skb mark on IFE action" depends on NET_ACT_IFE diff --git a/net/sched/Makefile b/net/sched/Makefile index 5078ea84e6ad..d9f60434e7d7 100644 --- a/net/sched/Makefile +++ b/net/sched/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_NET_IFE_SKBTCINDEX) +=3D act_meta_skbtcindex= .o obj-$(CONFIG_NET_ACT_TUNNEL_KEY)+=3D act_tunnel_key.o obj-$(CONFIG_NET_ACT_CT) +=3D act_ct.o obj-$(CONFIG_NET_ACT_GATE) +=3D act_gate.o +obj-$(CONFIG_NET_ACT_FRER) +=3D act_frer.o obj-$(CONFIG_NET_SCH_FIFO) +=3D sch_fifo.o obj-$(CONFIG_NET_SCH_HTB) +=3D sch_htb.o obj-$(CONFIG_NET_SCH_HFSC) +=3D sch_hfsc.o diff --git a/net/sched/act_frer.c b/net/sched/act_frer.c new file mode 100644 index 000000000000..7b6db643788d --- /dev/null +++ b/net/sched/act_frer.c @@ -0,0 +1,835 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright 2026 NXP */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* R-TAG wire structures (IEEE 802.1CB 7.8) */ +/* ------------------------------------------------------------------ */ + +struct r_tag { + __be16 reserved; + __be16 sequence_nr; + __be16 encap_proto; +} __packed; + +static struct tc_action_ops act_frer_ops; + +/* ------------------------------------------------------------------ */ +/* Recovery reset machinery */ +/* ------------------------------------------------------------------ */ + +struct frer_rcvy_work { + struct work_struct work; + struct frer_rcvy *rcvy; +}; + +static void frer_rcvy_reset(struct frer_rcvy *rcvy) +{ + if (rcvy->alg =3D=3D TCA_FRER_RCVY_VECTOR_ALG) { + rcvy->rcvy_seq_num =3D (u32)(rcvy->seq_space - 1); + rcvy->seq_history =3D 0; + } + rcvy->take_any =3D true; + rcvy->stats_resets++; +} + +static void frer_rcvy_reset_work_fn(struct work_struct *work) +{ + struct frer_rcvy_work *rw =3D + container_of(work, struct frer_rcvy_work, work); + struct frer_rcvy *rcvy =3D rw->rcvy; + + spin_lock_bh(&rcvy->lock); + frer_rcvy_reset(rcvy); + spin_unlock_bh(&rcvy->lock); + kfree(rw); +} + +static enum hrtimer_restart frer_rcvy_hrtimer_fn(struct hrtimer *timer) +{ + struct frer_rcvy *rcvy =3D + container_of(timer, struct frer_rcvy, hrtimer); + struct frer_rcvy_work *rw; + + /* Allocate in GFP_ATOMIC context; if it fails the state is not + * reset this cycle - the next frame will attempt again. + */ + rw =3D kmalloc_obj(*rw); + if (rw) { + INIT_WORK(&rw->work, frer_rcvy_reset_work_fn); + rw->rcvy =3D rcvy; + schedule_work(&rw->work); + } + return HRTIMER_NORESTART; +} + +static void frer_rcvy_timer_restart(struct frer_rcvy *rcvy) +{ + if (rcvy->reset_msec) + hrtimer_start(&rcvy->hrtimer, + ms_to_ktime(rcvy->reset_msec), + HRTIMER_MODE_REL_SOFT); +} + +static void frer_rcvy_init_state(struct frer_rcvy *rcvy, u8 alg, + u8 history_len, u32 reset_msec, + bool take_no_seq) +{ + rcvy->alg =3D alg; + rcvy->history_len =3D history_len; + rcvy->reset_msec =3D reset_msec; + rcvy->seq_space =3D 1 << 16; + rcvy->take_no_seq =3D take_no_seq; + rcvy->take_any =3D true; + rcvy->rcvy_seq_num =3D (u32)(rcvy->seq_space - 1); + rcvy->seq_history =3D 0; + spin_lock_init(&rcvy->lock); + hrtimer_setup(&rcvy->hrtimer, frer_rcvy_hrtimer_fn, CLOCK_MONOTONIC, + HRTIMER_MODE_REL_SOFT); +} + +/* ------------------------------------------------------------------ */ +/* R-TAG helpers */ +/* ------------------------------------------------------------------ */ + +static int frer_rtag_push(struct sk_buff *skb, u16 seq_num) +{ + unsigned char *new_mac_header; + unsigned int data_offset; + unsigned int head_len; + struct vlan_ethhdr *vh; + struct ethhdr *eh; + struct r_tag *rtag; + __be16 *proto_ptr; + __be16 saved_proto; + + if (!skb_mac_header_was_set(skb)) + return -EINVAL; + + data_offset =3D skb->data - skb_mac_header(skb); + + if (skb_cow_head(skb, data_offset + sizeof(*rtag))) + return -ENOMEM; + + if (data_offset > 0) + skb_push(skb, data_offset); + + eh =3D eth_hdr(skb); + if (eth_type_vlan(eh->h_proto)) { + if (!pskb_may_pull(skb, sizeof(*vh))) + return -EINVAL; + eh =3D eth_hdr(skb); + vh =3D (struct vlan_ethhdr *)eh; + proto_ptr =3D &vh->h_vlan_encapsulated_proto; + head_len =3D sizeof(*vh); + } else { + if (!pskb_may_pull(skb, sizeof(*eh))) + return -EINVAL; + eh =3D eth_hdr(skb); + proto_ptr =3D &eh->h_proto; + head_len =3D sizeof(*eh); + } + + saved_proto =3D *proto_ptr; + *proto_ptr =3D htons(ETH_P_RTAG); + + skb_push(skb, sizeof(*rtag)); + skb_reset_mac_header(skb); + + new_mac_header =3D skb_mac_header(skb); + memmove(new_mac_header, (unsigned char *)eh, head_len); + + skb->protocol =3D htons(ETH_P_RTAG); + skb_set_network_header(skb, head_len); + if (data_offset > 0) + skb_pull(skb, data_offset); + + /* Write R-TAG after the Ethernet / VLAN header */ + rtag =3D (struct r_tag *)(new_mac_header + head_len); + rtag->reserved =3D 0; + rtag->sequence_nr =3D htons(seq_num); + rtag->encap_proto =3D saved_proto; + + return 0; +} + +static void frer_rtag_pop(struct sk_buff *skb) +{ + unsigned char *new_mac_header; + unsigned int data_offset; + unsigned int head_len; + struct vlan_ethhdr *vh; + struct ethhdr *eh; + struct r_tag *rtag; + __be16 *proto_ptr; + + data_offset =3D skb->data - skb_mac_header(skb); + if (data_offset > 0) + skb_push(skb, data_offset); + + eh =3D eth_hdr(skb); + if (eth_type_vlan(eh->h_proto)) { + vh =3D (struct vlan_ethhdr *)eh; + proto_ptr =3D &vh->h_vlan_encapsulated_proto; + head_len =3D sizeof(*vh); + } else { + proto_ptr =3D &eh->h_proto; + head_len =3D sizeof(*eh); + } + + if (*proto_ptr !=3D htons(ETH_P_RTAG)) + return; + + rtag =3D (struct r_tag *)((unsigned char *)eh + head_len); + *proto_ptr =3D rtag->encap_proto; + + skb->protocol =3D rtag->encap_proto; + + skb_postpull_rcsum(skb, rtag, sizeof(struct r_tag)); + skb_pull(skb, sizeof(*rtag)); + skb_reset_mac_header(skb); + + new_mac_header =3D skb_mac_header(skb); + memmove(new_mac_header, (unsigned char *)eh, head_len); + + skb_set_network_header(skb, head_len); + if (data_offset > 0) + skb_pull(skb, data_offset); +} + +static int frer_rtag_decode(struct sk_buff *skb, int *seq) +{ + unsigned int data_offset; + struct vlan_ethhdr *vh; + unsigned int head_len; + struct ethhdr *eh; + struct r_tag *rtag; + __be16 *proto_ptr; + + if (!skb_mac_header_was_set(skb)) + return -EINVAL; + + data_offset =3D skb->data - skb_mac_header(skb); + + if (skb_cow_head(skb, data_offset)) + return -ENOMEM; + + if (data_offset > 0) + skb_push(skb, data_offset); + + eh =3D eth_hdr(skb); + if (eth_type_vlan(eh->h_proto)) { + if (!pskb_may_pull(skb, sizeof(*vh) + sizeof(*rtag))) + return -EINVAL; + eh =3D eth_hdr(skb); + vh =3D (struct vlan_ethhdr *)eh; + proto_ptr =3D &vh->h_vlan_encapsulated_proto; + head_len =3D sizeof(*vh); + } else { + if (!pskb_may_pull(skb, sizeof(*eh) + sizeof(*rtag))) + return -EINVAL; + eh =3D eth_hdr(skb); + proto_ptr =3D &eh->h_proto; + head_len =3D sizeof(*eh); + } + + if (data_offset > 0) + skb_pull(skb, data_offset); + + if (*proto_ptr !=3D htons(ETH_P_RTAG)) { + *seq =3D -1; + return 0; + } + + rtag =3D (struct r_tag *)((unsigned char *)eh + head_len); + + *seq =3D (int)ntohs(rtag->sequence_nr); + + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Recovery algorithms (called with rcvy->lock held) */ +/* ------------------------------------------------------------------ */ + +/* Returns true =3D pass frame, false =3D discard frame. + * @individual: when true, restart the reset timer even on discarded frames + * (rogue/duplicate), as required for Individual Recovery (IEEE 802.1CB = 7.5). + */ +static bool frer_vector_alg(struct frer_rcvy *rcvy, int seq, bool individu= al) +{ + int delta; + bool restart_timer =3D false; + bool pass; + + if (seq < 0) { + /* No R-TAG present */ + rcvy->stats_tagless_pkts++; + if (rcvy->take_no_seq) { + restart_timer =3D true; + pass =3D true; + } else { + pass =3D false; + } + goto out; + } + + if (rcvy->take_any) { + /* First frame after reset: accept unconditionally */ + rcvy->take_any =3D false; + rcvy->rcvy_seq_num =3D (u32)seq; + rcvy->seq_history =3D BIT(0); + restart_timer =3D true; + pass =3D true; + goto out; + } + + delta =3D (seq - (int)rcvy->rcvy_seq_num) & + (int)(rcvy->seq_space - 1); + /* Map delta > seq_space/2 to negative (signed wrap) */ + if ((u32)delta & (u32)(rcvy->seq_space / 2)) + delta -=3D (int)rcvy->seq_space; + + if (delta >=3D (int)rcvy->history_len || + delta <=3D -(int)rcvy->history_len) { + /* Packet is out-of-range (rogue). */ + rcvy->stats_rogue_pkts++; + if (individual) + restart_timer =3D true; + pass =3D false; + goto out; + } + + if (delta <=3D 0) { + /* Packet is old: check whether already seen. */ + if (rcvy->seq_history & BIT(-delta)) { + if (individual) + restart_timer =3D true; + /* Already received */ + pass =3D false; + } else { + /* Out-of-order but not yet seen */ + rcvy->seq_history |=3D BIT(-delta); + rcvy->stats_out_of_order_pkts++; + restart_timer =3D true; + pass =3D true; + } + goto out; + } + + /* delta > 0: frame is newer than expected */ + if (delta !=3D 1) + rcvy->stats_out_of_order_pkts++; + + /* Shift history forward, counting any gaps as lost */ + while (--delta) { + if (!(rcvy->seq_history & BIT(rcvy->history_len - 1))) + rcvy->stats_lost_pkts++; + rcvy->seq_history <<=3D 1; + } + if (!(rcvy->seq_history & BIT(rcvy->history_len - 1))) + rcvy->stats_lost_pkts++; + rcvy->seq_history =3D (rcvy->seq_history << 1) | BIT(0); + rcvy->rcvy_seq_num =3D (u32)seq; + restart_timer =3D true; + pass =3D true; + +out: + if (restart_timer) + frer_rcvy_timer_restart(rcvy); + return pass; +} + +static bool frer_match_alg(struct frer_rcvy *rcvy, int seq, bool individua= l) +{ + if (seq < 0) { + /* No R-TAG: Match alg cannot deduplicate, always pass. */ + rcvy->stats_tagless_pkts++; + return true; + } + + if (rcvy->take_any) { + rcvy->take_any =3D false; + rcvy->rcvy_seq_num =3D (u32)seq; + frer_rcvy_timer_restart(rcvy); + return true; + } + + if ((u32)seq =3D=3D rcvy->rcvy_seq_num) { + /* Duplicate */ + if (individual) + frer_rcvy_timer_restart(rcvy); + return false; + } + + /* New sequence number: accept and update */ + if ((u32)seq !=3D ((rcvy->rcvy_seq_num + 1) % rcvy->seq_space)) + rcvy->stats_out_of_order_pkts++; + rcvy->rcvy_seq_num =3D (u32)seq; + frer_rcvy_timer_restart(rcvy); + return true; +} + +/* ------------------------------------------------------------------ */ +/* Netlink policy */ +/* ------------------------------------------------------------------ */ + +static const struct nla_policy frer_policy[TCA_FRER_MAX + 1] =3D { + [TCA_FRER_PARMS] =3D NLA_POLICY_EXACT_LEN(sizeof(struct tc_fre= r)), + [TCA_FRER_FUNC] =3D { .type =3D NLA_U8 }, + [TCA_FRER_TAG_TYPE] =3D { .type =3D NLA_U8 }, + [TCA_FRER_RCVY_INDIVIDUAL] =3D { .type =3D NLA_FLAG }, + [TCA_FRER_RCVY_ALG] =3D { .type =3D NLA_U8 }, + [TCA_FRER_RCVY_HISTORY_LEN] =3D NLA_POLICY_RANGE(NLA_U8, 1, 32), + [TCA_FRER_RCVY_RESET_MSEC] =3D { .type =3D NLA_U32 }, + [TCA_FRER_RCVY_TAKE_NO_SEQ] =3D { .type =3D NLA_FLAG }, + [TCA_FRER_RCVY_TAG_POP] =3D { .type =3D NLA_FLAG }, +}; + +/* ------------------------------------------------------------------ */ +/* Action init */ +/* ------------------------------------------------------------------ */ + +static int tcf_frer_init(struct net *net, struct nlattr *nla, + struct nlattr *est, struct tc_action **a, + struct tcf_proto *tp, u32 flags, + struct netlink_ext_ack *extack) +{ + struct tc_action_net *tn =3D net_generic(net, act_frer_ops.net_id); + bool bind =3D flags & TCA_ACT_FLAGS_BIND; + struct nlattr *tb[TCA_FRER_MAX + 1]; + struct tcf_chain *goto_ch =3D NULL; + struct tcf_frer *f; + struct tc_frer *parm; + bool exists =3D false; + int ret =3D 0, err, index; + u8 func, tag_type; + + if (!nla) { + NL_SET_ERR_MSG_MOD(extack, "frer: attributes required"); + return -EINVAL; + } + + err =3D nla_parse_nested(tb, TCA_FRER_MAX, nla, frer_policy, extack); + if (err < 0) + return err; + + if (!tb[TCA_FRER_PARMS]) { + NL_SET_ERR_MSG_MOD(extack, "frer: TCA_FRER_PARMS missing"); + return -EINVAL; + } + if (!tb[TCA_FRER_FUNC]) { + NL_SET_ERR_MSG_MOD(extack, "frer: TCA_FRER_FUNC missing"); + return -EINVAL; + } + if (!tb[TCA_FRER_TAG_TYPE]) { + NL_SET_ERR_MSG_MOD(extack, "frer: TCA_FRER_TAG_TYPE missing"); + return -EINVAL; + } + + func =3D nla_get_u8(tb[TCA_FRER_FUNC]); + tag_type =3D nla_get_u8(tb[TCA_FRER_TAG_TYPE]); + + if (func !=3D TCA_FRER_FUNC_PUSH && func !=3D TCA_FRER_FUNC_RECOVER) { + NL_SET_ERR_MSG_MOD(extack, "frer: unknown func"); + return -EINVAL; + } + if (tag_type !=3D TCA_FRER_TAG_RTAG) { + NL_SET_ERR_MSG_MOD(extack, "frer: only rtag supported"); + return -EOPNOTSUPP; + } + + parm =3D nla_data(tb[TCA_FRER_PARMS]); + index =3D parm->index; + + err =3D tcf_idr_check_alloc(tn, &index, a, bind); + if (err < 0) + return err; + exists =3D err; + + if (exists && bind) + return ACT_P_BOUND; + + if (!exists) { + ret =3D tcf_idr_create_from_flags(tn, index, est, a, + &act_frer_ops, bind, flags); + if (ret) { + tcf_idr_cleanup(tn, index); + return ret; + } + ret =3D ACT_P_CREATED; + } else if (!(flags & TCA_ACT_FLAGS_REPLACE)) { + tcf_idr_release(*a, bind); + return -EEXIST; + } + + err =3D tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); + if (err < 0) + goto release_idr; + + f =3D to_frer(*a); + + spin_lock_bh(&f->tcf_lock); + goto_ch =3D tcf_action_set_ctrlact(*a, parm->action, goto_ch); + f->func =3D func; + f->tag_type =3D tag_type; + f->tag_pop =3D !!tb[TCA_FRER_RCVY_TAG_POP]; + + if (func =3D=3D TCA_FRER_FUNC_PUSH) { + if (ret =3D=3D ACT_P_CREATED) { + spin_lock_init(&f->seqgen.lock); + f->seqgen.seq_space =3D 1 << 16; + } + /* gen_seq_num starts at 0 on creation; preserved on replace */ + } else { + u8 alg =3D tb[TCA_FRER_RCVY_ALG] ? + nla_get_u8(tb[TCA_FRER_RCVY_ALG]) : + TCA_FRER_RCVY_VECTOR_ALG; + u8 history_len =3D tb[TCA_FRER_RCVY_HISTORY_LEN] ? + nla_get_u8(tb[TCA_FRER_RCVY_HISTORY_LEN]) : 32; + u32 reset_msec =3D tb[TCA_FRER_RCVY_RESET_MSEC] ? + nla_get_u32(tb[TCA_FRER_RCVY_RESET_MSEC]) : 0; + bool take_no_seq =3D !!tb[TCA_FRER_RCVY_TAKE_NO_SEQ]; + + if (alg !=3D TCA_FRER_RCVY_VECTOR_ALG && + alg !=3D TCA_FRER_RCVY_MATCH_ALG) { + spin_unlock_bh(&f->tcf_lock); + NL_SET_ERR_MSG_MOD(extack, "frer: unknown recovery algorithm"); + err =3D -EINVAL; + goto release_idr; + } + + f->individual =3D !!tb[TCA_FRER_RCVY_INDIVIDUAL]; + + /* Cancel any running reset timer before re-initialising. */ + if (ret !=3D ACT_P_CREATED && f->rcvy.reset_msec) { + spin_unlock_bh(&f->tcf_lock); + hrtimer_cancel(&f->rcvy.hrtimer); + spin_lock_bh(&f->tcf_lock); + } + + frer_rcvy_init_state(&f->rcvy, alg, history_len, + reset_msec, take_no_seq); + } + + spin_unlock_bh(&f->tcf_lock); + + if (goto_ch) + tcf_chain_put_by_act(goto_ch); + + return ret; + +release_idr: + tcf_idr_release(*a, bind); + return err; +} + +/* ------------------------------------------------------------------ */ +/* Data path */ +/* ------------------------------------------------------------------ */ + +static int tcf_frer_act(struct sk_buff *skb, const struct tc_action *a, + struct tcf_result *res) +{ + struct tcf_frer *f =3D to_frer(a); + int retval; + + tcf_lastuse_update(&f->tcf_tm); + tcf_action_update_bstats(&f->common, skb); + retval =3D READ_ONCE(f->tcf_action); + + if (f->func =3D=3D TCA_FRER_FUNC_PUSH) { + struct frer_seqgen *sg =3D &f->seqgen; + u16 seq; + + spin_lock(&sg->lock); + seq =3D (u16)sg->gen_seq_num; + if (++sg->gen_seq_num >=3D sg->seq_space) + sg->gen_seq_num =3D 0; + sg->stats_pkts++; + spin_unlock(&sg->lock); + + if (frer_rtag_push(skb, seq) < 0) { + tcf_action_inc_drop_qstats(&f->common); + return TC_ACT_SHOT; + } + } else { + struct frer_rcvy *rcvy =3D &f->rcvy; + bool pass; + int seq; + + if (frer_rtag_decode(skb, &seq) < 0) { + tcf_action_inc_drop_qstats(&f->common); + return TC_ACT_SHOT; + } + + spin_lock(&rcvy->lock); + if (rcvy->alg =3D=3D TCA_FRER_RCVY_VECTOR_ALG) + pass =3D frer_vector_alg(rcvy, seq, f->individual); + else + pass =3D frer_match_alg(rcvy, seq, f->individual); + + if (pass) { + rcvy->stats_passed_pkts++; + spin_unlock(&rcvy->lock); + if (f->tag_pop) + frer_rtag_pop(skb); + return retval; + } + + rcvy->stats_discarded_pkts++; + spin_unlock(&rcvy->lock); + return TC_ACT_SHOT; + } + + return retval; +} + +/* ------------------------------------------------------------------ */ +/* Dump */ +/* ------------------------------------------------------------------ */ + +static int tcf_frer_dump(struct sk_buff *skb, struct tc_action *a, + int bind, int ref) +{ + unsigned char *b =3D skb_tail_pointer(skb); + struct tcf_frer *f =3D to_frer(a); + struct tc_frer opt =3D { + .index =3D f->tcf_index, + .refcnt =3D refcount_read(&f->tcf_refcnt) - ref, + .bindcnt =3D atomic_read(&f->tcf_bindcnt) - bind, + }; + struct tcf_t t; + + spin_lock_bh(&f->tcf_lock); + opt.action =3D f->tcf_action; + + if (nla_put(skb, TCA_FRER_PARMS, sizeof(opt), &opt)) + goto nla_put_failure; + if (nla_put_u8(skb, TCA_FRER_FUNC, f->func)) + goto nla_put_failure; + if (nla_put_u8(skb, TCA_FRER_TAG_TYPE, f->tag_type)) + goto nla_put_failure; + if (f->tag_pop && nla_put_flag(skb, TCA_FRER_RCVY_TAG_POP)) + goto nla_put_failure; + + if (f->func =3D=3D TCA_FRER_FUNC_PUSH) { + spin_lock(&f->seqgen.lock); + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_SEQGEN_PKTS, + f->seqgen.stats_pkts, TCA_FRER_PAD)) { + spin_unlock(&f->seqgen.lock); + goto nla_put_failure; + } + spin_unlock(&f->seqgen.lock); + } else { + u64 tagless, ooo, rogue, lost, resets, passed, discarded; + struct frer_rcvy *rcvy =3D &f->rcvy; + + spin_lock(&rcvy->lock); + tagless =3D rcvy->stats_tagless_pkts; + ooo =3D rcvy->stats_out_of_order_pkts; + rogue =3D rcvy->stats_rogue_pkts; + lost =3D rcvy->stats_lost_pkts; + resets =3D rcvy->stats_resets; + passed =3D rcvy->stats_passed_pkts; + discarded =3D rcvy->stats_discarded_pkts; + spin_unlock(&rcvy->lock); + + if (f->individual && nla_put_flag(skb, TCA_FRER_RCVY_INDIVIDUAL)) + goto nla_put_failure; + if (nla_put_u8(skb, TCA_FRER_RCVY_ALG, rcvy->alg)) + goto nla_put_failure; + if (nla_put_u8(skb, TCA_FRER_RCVY_HISTORY_LEN, rcvy->history_len)) + goto nla_put_failure; + if (nla_put_u32(skb, TCA_FRER_RCVY_RESET_MSEC, rcvy->reset_msec)) + goto nla_put_failure; + if (rcvy->take_no_seq && nla_put_flag(skb, TCA_FRER_RCVY_TAKE_NO_SEQ)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_TAGLESS_PKTS, + tagless, TCA_FRER_PAD)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_OUT_OF_ORDER_PKTS, + ooo, TCA_FRER_PAD)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_ROGUE_PKTS, + rogue, TCA_FRER_PAD)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_LOST_PKTS, + lost, TCA_FRER_PAD)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_RESETS, + resets, TCA_FRER_PAD)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_PASSED_PKTS, + passed, TCA_FRER_PAD)) + goto nla_put_failure; + if (nla_put_u64_64bit(skb, TCA_FRER_STATS_DISCARDED_PKTS, + discarded, TCA_FRER_PAD)) + goto nla_put_failure; + } + + tcf_tm_dump(&t, &f->tcf_tm); + if (nla_put_64bit(skb, TCA_FRER_TM, sizeof(t), &t, TCA_FRER_PAD)) + goto nla_put_failure; + + spin_unlock_bh(&f->tcf_lock); + return skb->len; + +nla_put_failure: + spin_unlock_bh(&f->tcf_lock); + nlmsg_trim(skb, b); + return -1; +} + +/* ------------------------------------------------------------------ */ +/* Cleanup */ +/* ------------------------------------------------------------------ */ + +static void tcf_frer_cleanup(struct tc_action *a) +{ + struct tcf_frer *f =3D to_frer(a); + + if (f->func =3D=3D TCA_FRER_FUNC_RECOVER) + hrtimer_cancel(&f->rcvy.hrtimer); +} + +/* ------------------------------------------------------------------ */ +/* Walker / search / stats / fill-size / offload */ +/* ------------------------------------------------------------------ */ + +static int tcf_frer_walker(struct net *net, struct sk_buff *skb, + struct netlink_callback *cb, int type, + const struct tc_action_ops *ops, + struct netlink_ext_ack *extack) +{ + struct tc_action_net *tn =3D net_generic(net, act_frer_ops.net_id); + + return tcf_generic_walker(tn, skb, cb, type, ops, extack); +} + +static void tcf_frer_stats_update(struct tc_action *a, u64 bytes, u64 pack= ets, + u64 drops, u64 lastuse, bool hw) +{ + struct tcf_frer *f =3D to_frer(a); + struct tcf_t *tm =3D &f->tcf_tm; + + tcf_action_update_stats(a, bytes, packets, drops, hw); + tm->lastuse =3D max_t(u64, tm->lastuse, lastuse); +} + +static size_t tcf_frer_get_fill_size(const struct tc_action *act) +{ + return nla_total_size(sizeof(struct tc_frer)) /* TCA_FRER_PARMS */ + + nla_total_size(sizeof(u8)) /* TCA_FRER_FUNC */ + + nla_total_size(sizeof(u8)) /* TCA_FRER_TAG_TYPE */ + + nla_total_size(0) /* TCA_FRER_RCVY_TAG_POP (flag) */ + + nla_total_size(0) /* TCA_FRER_RCVY_INDIVIDUAL (flag) */ + + nla_total_size(sizeof(u8)) /* TCA_FRER_RCVY_ALG */ + + nla_total_size(sizeof(u8)) /* TCA_FRER_RCVY_HISTORY_LEN */ + + nla_total_size(sizeof(u32)) /* TCA_FRER_RCVY_RESET_MSEC */ + + nla_total_size(0) /* TCA_FRER_RCVY_TAKE_NO_SEQ (flag) */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_TAGLESS_PKTS */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_OUT_OF_ORDER_PKTS = */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_ROGUE_PKTS */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_LOST_PKTS */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_RESETS */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_PASSED_PKTS */ + + nla_total_size_64bit(sizeof(u64)) /* TCA_FRER_STATS_DISCARDED_PKTS */ + + nla_total_size_64bit(sizeof(struct tcf_t)); /* TCA_FRER_TM */ +} + +static int tcf_frer_offload_act_setup(struct tc_action *act, void *entry_d= ata, + u32 *index_inc, bool bind, + struct netlink_ext_ack *extack) +{ + if (bind) { + struct flow_action_entry *entry =3D entry_data; + struct tcf_frer *f =3D to_frer(act); + + entry->id =3D FLOW_ACTION_FRER; + entry->frer.func =3D f->func; + entry->frer.tag_type =3D f->tag_type; + entry->frer.tag_pop =3D f->tag_pop; + + if (f->func !=3D TCA_FRER_FUNC_PUSH) { + entry->frer.individual =3D f->individual; + entry->frer.rcvy_alg =3D f->rcvy.alg; + entry->frer.rcvy_history_len =3D f->rcvy.history_len; + entry->frer.rcvy_reset_msec =3D f->rcvy.reset_msec; + entry->frer.take_no_seq =3D f->rcvy.take_no_seq; + } + *index_inc =3D 1; + } else { + struct flow_offload_action *fl_action =3D entry_data; + + fl_action->id =3D FLOW_ACTION_FRER; + } + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Module glue */ +/* ------------------------------------------------------------------ */ + +static struct tc_action_ops act_frer_ops =3D { + .kind =3D "frer", + .id =3D TCA_ID_FRER, + .owner =3D THIS_MODULE, + .act =3D tcf_frer_act, + .init =3D tcf_frer_init, + .cleanup =3D tcf_frer_cleanup, + .dump =3D tcf_frer_dump, + .walk =3D tcf_frer_walker, + .stats_update =3D tcf_frer_stats_update, + .get_fill_size =3D tcf_frer_get_fill_size, + .offload_act_setup =3D tcf_frer_offload_act_setup, + .size =3D sizeof(struct tcf_frer), +}; + +static __net_init int frer_init_net(struct net *net) +{ + struct tc_action_net *tn =3D net_generic(net, act_frer_ops.net_id); + + return tc_action_net_init(net, tn, &act_frer_ops); +} + +static void __net_exit frer_exit_net(struct list_head *net_list) +{ + tc_action_net_exit(net_list, act_frer_ops.net_id); +} + +static struct pernet_operations frer_net_ops =3D { + .init =3D frer_init_net, + .exit_batch =3D frer_exit_net, + .id =3D &act_frer_ops.net_id, + .size =3D sizeof(struct tc_action_net), +}; + +static int __init frer_init_module(void) +{ + return tcf_register_action(&act_frer_ops, &frer_net_ops); +} + +static void __exit frer_cleanup_module(void) +{ + tcf_unregister_action(&act_frer_ops, &frer_net_ops); +} + +module_init(frer_init_module); +module_exit(frer_cleanup_module); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("IEEE 802.1CB FRER tc action"); --=20 2.17.1 From nobody Sat Jul 25 05:31:26 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E0ED83EB0E5; Fri, 17 Jul 2026 09:55:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282131; cv=none; b=LRGIWbjBCGrqJuljUf88Avh3crRiBp79zFh8fSNcoz1Ocm9H6eWlNe7zHiN8yCaOFKgg5iWJWIMRLSqbLqX203WcEOy1Fo1g6pW9iCB/KGCsqCLUkt12RYtKaovtMaFZbpQ6XpOVx/BvtQKVqhRAqBwc9FmBncvCYSNIdk/m1po= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282131; c=relaxed/simple; bh=brgwXwH1SqQLIPv/bf60pAhzZvaKhEL4gt1DB6hekys=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=ann2sVJH3Yfm8C3rG8wKTbxlagL4mVOnERmxGKWmEyM7TPKX0nSJQWCLBFhpBwYjfYxGIP5l8YKEdyHtea6AVpS5GKAyXc1Rndgvlt+9HtO0q6X3380JsYNVcGSZIJT+ZLmhHAOkUueuDnx1Ilxj169DYpaATlTle7MADcq6/q0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 227A02001FB; Fri, 17 Jul 2026 11:55:27 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id B702D2001EB; Fri, 17 Jul 2026 11:55:26 +0200 (CEST) Received: from localhost.localdomain (mega.ap.freescale.net [10.192.208.232]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 7835218000B5; Fri, 17 Jul 2026 17:55:24 +0800 (+08) From: Xiaoliang Yang To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, jhs@mojatatu.com, jiri@resnulli.us, horms@kernel.org, shuah@kernel.org, vladimir.oltean@nxp.com, vinicius.gomes@intel.com, fejes@inf.elte.hu, xiaoliang.yang_1@nxp.com Subject: [PATCH net-next 5/6] selftest: add tc-testing JSON test cases for act_frer Date: Fri, 17 Jul 2026 17:55:48 +0800 Message-Id: <20260717095549.10565-6-xiaoliang.yang_1@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> References: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Add a tc-testing JSON file covering the FRER (IEEE 802.1CB Frame Replication and Elimination for Reliability) tc action (act_frer). The test suite contains 32 test cases and exercises: - Creating push and recover actions with default and explicit parameters (tag-type, alg vector/match, history-length, reset-time, tag-pop, individual, take-no-seq) - Boundary values for history-length (1 and 32) and reset-time (0) - Combining multiple flags (frer_0011, frer_0012) - Statistics output format for push (SeqGen) and recover (passed, discarded, tagless, out-of-order, rogue, lost, resets) - Replace and delete operations - Flush all actions - Duplicate-index failure (expExitCode 255) - Control actions (continue, pipe) placed after the index token - Binding push and recover actions to egress/ingress clsact filters - Sharing a recover action across two filters and verifying the reference count increments - not_in_hw flag present in show output Signed-off-by: Xiaoliang Yang --- .../tc-testing/tc-tests/actions/frer.json | 785 ++++++++++++++++++ 1 file changed, 785 insertions(+) create mode 100644 tools/testing/selftests/tc-testing/tc-tests/actions/fre= r.json diff --git a/tools/testing/selftests/tc-testing/tc-tests/actions/frer.json = b/tools/testing/selftests/tc-testing/tc-tests/actions/frer.json new file mode 100644 index 000000000000..d5be6ae156f7 --- /dev/null +++ b/tools/testing/selftests/tc-testing/tc-tests/actions/frer.json @@ -0,0 +1,785 @@ +[ + { + "id": "frer_0001", + "name": "Create frer push action with default parameters", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer push index 1", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 1", + "matchPattern": "action order [0-9]+: frer push tag-type rtag index 1", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0002", + "name": "Create frer push action with explicit tag-type rtag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer push tag-type rtag index = 2", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 2", + "matchPattern": "action order [0-9]+: frer push tag-type rtag index 2", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0003", + "name": "Create frer recover action with default parameters", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover index 10", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 10", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 10 alg vector history-length [0-9]+ reset-time [0-9]+", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0004", + "name": "Create frer recover action with vector algorithm explicit", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector index = 11", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 11", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 11 alg vector", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0005", + "name": "Create frer recover action with match algorithm", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg match index 1= 2", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 12", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 12 alg match", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0006", + "name": "Create frer recover action with history-length 16", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector histor= y-length 16 index 13", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 13", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 13 alg vector history-length 16", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0007", + "name": "Create frer recover action with reset-time 2000", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector reset-= time 2000 index 14", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 14", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 14 alg vector history-length [0-9]+ reset-time 2000", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0008", + "name": "Create frer recover action with tag-pop flag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover tag-pop index 15", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 15", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 15.*tag-pop", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0009", + "name": "Create frer recover action with individual flag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover individual index = 16", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 16", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 16.*individual", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0010", + "name": "Create frer recover action with take-no-seq flag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover take-no-seq index= 17", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 17", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 17.*take-no-seq", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0011", + "name": "Create frer recover action with all parameters combined", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector histor= y-length 16 reset-time 1000 tag-pop individual index 20", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 20", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 20 individual alg vector history-length 16 reset-time 1000 tag-pop", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0012", + "name": "Create frer recover action with match alg and all flags", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg match take-no= -seq tag-pop individual index 21", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 21", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 21 individual alg match history-length [0-9]+ reset-time [0-9]+ tag-pop ta= ke-no-seq", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0013", + "name": "Show frer push action SeqGen statistics (zero after create)", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1" + ], + "cmdUnderTest": "$TC -s actions show action frer index 1", + "expExitCode": "0", + "verifyCmd": "$TC -s actions show action frer index 1", + "matchPattern": "SeqGen packets: 0", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0014", + "name": "Show frer recover action Statistics line (zero after create)", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer recover alg vector history-length 16 re= set-time 1000 tag-pop index 10" + ], + "cmdUnderTest": "$TC -s actions show action frer index 10", + "expExitCode": "0", + "verifyCmd": "$TC -s actions show action frer index 10", + "matchPattern": "Statistics: passed=3D0 discarded=3D0 tagless=3D0 out-= of-order=3D0 rogue=3D0 lost=3D0 resets=3D0", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0015", + "name": "Show frer recover action Statistics fields present", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer recover index 10" + ], + "cmdUnderTest": "$TC -s actions show action frer index 10", + "expExitCode": "0", + "verifyCmd": "$TC -s actions show action frer index 10", + "matchPattern": "Statistics: passed=3D[0-9]+ discarded=3D[0-9]+ tagles= s=3D[0-9]+ out-of-order=3D[0-9]+ rogue=3D[0-9]+ lost=3D[0-9]+ resets=3D[0-9= ]+", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0016", + "name": "Replace frer push action (same index)", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1" + ], + "cmdUnderTest": "$TC actions replace action frer push index 1", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 1", + "matchPattern": "action order [0-9]+: frer push tag-type rtag index 1", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0017", + "name": "Replace frer recover action changing algorithm from vector to= match", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer recover alg vector index 10" + ], + "cmdUnderTest": "$TC actions replace action frer recover alg match ind= ex 10", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 10", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 10 alg match", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0018", + "name": "Delete frer push action by index", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1" + ], + "cmdUnderTest": "$TC actions del action frer index 1", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer", + "matchPattern": "frer push tag-type rtag index 1", + "matchCount": "0", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0019", + "name": "Flush all frer actions", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1", + "$TC actions add action frer recover index 10", + "$TC actions add action frer recover index 11" + ], + "cmdUnderTest": "$TC actions flush action frer", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer", + "matchPattern": "action order [0-9]+: frer", + "matchCount": "0", + "teardown": [ + "$TC actions flush action frer 2>/dev/null || true" + ] + }, + { + "id": "frer_0020", + "name": "Add duplicate frer action index fails without replace flag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1" + ], + "cmdUnderTest": "$TC actions add action frer push index 1", + "expExitCode": "255", + "verifyCmd": "$TC actions show action frer index 1", + "matchPattern": "action order [0-9]+: frer push tag-type rtag index 1", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0021", + "name": "Create frer push action with continue control action", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer push index 1 continue", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 1", + "matchPattern": "action order [0-9]+: frer push tag-type rtag index 1.= *control continue", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0022", + "name": "Create frer recover action with pipe control action", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover index 10 pipe", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 10", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 10.*control pipe", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0023", + "name": "Create frer recover action history-length minimum boundary (1= )", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector histor= y-length 1 index 30", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 30", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 30 alg vector history-length 1", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0024", + "name": "Create frer recover action history-length maximum boundary (3= 2)", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector histor= y-length 32 index 31", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 31", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 31 alg vector history-length 32", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0025", + "name": "Create frer recover action with reset-time 0 (timer disabled)= ", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ] + ], + "cmdUnderTest": "$TC actions add action frer recover alg vector reset-= time 0 index 32", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 32", + "matchPattern": "action order [0-9]+: frer recover tag-type rtag index= 32 alg vector history-length [0-9]+ reset-time 0", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0026", + "name": "List all frer actions shows correct count", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1", + "$TC actions add action frer recover alg vector index 10", + "$TC actions add action frer recover alg match tag-pop index 11" + ], + "cmdUnderTest": "$TC actions show action frer", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer", + "matchPattern": "action order [0-9]+: frer", + "matchCount": "3", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0027", + "name": "Bind frer push action to egress clsact filter", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "ip link del frer_dummy 2>/dev/null || true", + "ip link add frer_dummy type dummy", + "ip link set frer_dummy up", + "$TC qdisc add dev frer_dummy clsact" + ], + "cmdUnderTest": "$TC filter add dev frer_dummy egress protocol ip flow= er skip_hw action frer push index 1", + "expExitCode": "0", + "verifyCmd": "$TC filter show dev frer_dummy egress", + "matchPattern": "frer push tag-type rtag index 1", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev frer_dummy clsact", + "$TC actions flush action frer", + "ip link del frer_dummy" + ] + }, + { + "id": "frer_0028", + "name": "Bind frer recover action to ingress clsact filter", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "ip link del frer_dummy 2>/dev/null || true", + "ip link add frer_dummy type dummy", + "ip link set frer_dummy up", + "$TC qdisc add dev frer_dummy clsact" + ], + "cmdUnderTest": "$TC filter add dev frer_dummy ingress protocol all fl= ower skip_hw action frer recover alg vector history-length 16 reset-time 10= 00 tag-pop index 10", + "expExitCode": "0", + "verifyCmd": "$TC filter show dev frer_dummy ingress", + "matchPattern": "frer recover tag-type rtag index 10 alg vector histor= y-length 16 reset-time 1000", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev frer_dummy clsact", + "$TC actions flush action frer", + "ip link del frer_dummy" + ] + }, + { + "id": "frer_0029", + "name": "Share frer recover action across two ingress filters (refcoun= t check)", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "ip link del frer_a 2>/dev/null || true", + "ip link del frer_b 2>/dev/null || true", + "ip link add frer_a type dummy", + "ip link add frer_b type dummy", + "ip link set frer_a up", + "ip link set frer_b up", + "$TC qdisc add dev frer_a clsact", + "$TC qdisc add dev frer_b clsact", + "$TC filter add dev frer_a ingress protocol all flower skip_hw actio= n frer recover alg vector history-length 16 tag-pop index 10" + ], + "cmdUnderTest": "$TC filter add dev frer_b ingress protocol all flower= skip_hw action frer recover index 10", + "expExitCode": "0", + "verifyCmd": "$TC -s actions show action frer index 10", + "matchPattern": "ref [2-9][0-9]*", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev frer_a clsact", + "$TC qdisc del dev frer_b clsact", + "$TC actions flush action frer", + "ip link del frer_a", + "ip link del frer_b" + ] + }, + { + "id": "frer_0030", + "name": "frer push action refcount increments when bound to filter", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "ip link del frer_dummy 2>/dev/null || true", + "ip link add frer_dummy type dummy", + "ip link set frer_dummy up", + "$TC qdisc add dev frer_dummy clsact", + "$TC actions add action frer push index 1" + ], + "cmdUnderTest": "$TC filter add dev frer_dummy egress protocol ip flow= er skip_hw action frer push index 1", + "expExitCode": "0", + "verifyCmd": "$TC -s actions show action frer index 1", + "matchPattern": "ref [2-9][0-9]*", + "matchCount": "1", + "teardown": [ + "$TC qdisc del dev frer_dummy clsact", + "$TC actions flush action frer", + "ip link del frer_dummy" + ] + }, + { + "id": "frer_0031", + "name": "frer push output shows not_in_hw flag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer push index 1" + ], + "cmdUnderTest": "$TC actions show action frer index 1", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 1", + "matchPattern": "not_in_hw", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + }, + { + "id": "frer_0032", + "name": "frer recover output shows not_in_hw flag", + "category": [ + "actions", + "frer" + ], + "setup": [ + [ + "modprobe act_frer", + 0, + 1 + ], + "$TC actions add action frer recover index 10" + ], + "cmdUnderTest": "$TC actions show action frer index 10", + "expExitCode": "0", + "verifyCmd": "$TC actions show action frer index 10", + "matchPattern": "not_in_hw", + "matchCount": "1", + "teardown": [ + "$TC actions flush action frer" + ] + } +] --=20 2.17.1 From nobody Sat Jul 25 05:31:26 2026 Received: from inva021.nxp.com (inva021.nxp.com [92.121.34.21]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DFD463EB102; Fri, 17 Jul 2026 09:55:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=92.121.34.21 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282134; cv=none; b=tYKMszo3Z+cuOBECG3iBwNwe+/VRmhxRlr0l/4p73YZ2OecRF1zBxPCtuqxpIkSzJxgUpKqwHPTNXu/4951KQz/VaCstzlnscKViaylIe5czSsvOEegvofZdwagf36aZ1c6veIlfyN5Ml+m+QM4d5SUBXsHmlkyU3trv/oegCfg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784282134; c=relaxed/simple; bh=6ByB7Okee7twuKSW8YtCinD8oB+AHI9omjxPGj5zexY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=Zj9T3wIWZJLTH++PKwo/jB2BbiC/8L3I15OQOAEYqr3cZtxdGR8bEBw7Zr97u6G188Qe7ncpZehiQ1mW4djbNygPBj73jqhmQGiNzFWWTCMgwWa2xYItoils4srgZcSNYpn/0JoPP39/ki/EzrzeLXZ737SE2MzAbt/OgE9wnnI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com; spf=pass smtp.mailfrom=nxp.com; arc=none smtp.client-ip=92.121.34.21 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=nxp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nxp.com Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 3EC0F200205; Fri, 17 Jul 2026 11:55:29 +0200 (CEST) Received: from aprdc01srsp001v.ap-rdc01.nxp.com (aprdc01srsp001v.ap-rdc01.nxp.com [165.114.16.16]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id AA2242001EB; Fri, 17 Jul 2026 11:55:28 +0200 (CEST) Received: from localhost.localdomain (mega.ap.freescale.net [10.192.208.232]) by aprdc01srsp001v.ap-rdc01.nxp.com (Postfix) with ESMTP id 51DA818000B0; Fri, 17 Jul 2026 17:55:26 +0800 (+08) From: Xiaoliang Yang To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org Cc: davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, jhs@mojatatu.com, jiri@resnulli.us, horms@kernel.org, shuah@kernel.org, vladimir.oltean@nxp.com, vinicius.gomes@intel.com, fejes@inf.elte.hu, xiaoliang.yang_1@nxp.com Subject: [PATCH net-next 6/6] selftests: net: add kselftest for IEEE 802.1CB FRER tc action Date: Fri, 17 Jul 2026 17:55:49 +0800 Message-Id: <20260717095549.10565-7-xiaoliang.yang_1@nxp.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> References: <20260717095549.10565-1-xiaoliang.yang_1@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Add frer_test.sh, a TAP-format kselftest script covering the FRER (IEEE 802.1CB Frame Replication and Elimination for Reliability) tc action (act_frer). Tests 1-4 use a bond-based two-namespace topology: ns_talker +---------------------------+ | bond0 (IP_SRC, balance-rr)| | slave: veth_a0 (frer push + mirror to veth_b0)| | slave: veth_b0 (frer push + mirror to veth_a0)| +-------+---------------+--+ | | veth_a0 veth_b0 | | veth_a1 veth_b1 | | +-------+---------------+--+ | bond1 (IP_DST, balance-rr)| | slave: veth_a1 (frer recover ingress) | | slave: veth_b1 (frer recover ingress) | +---------------------------+ ns_listener IP_SRC is assigned to bond0; IP_DST is assigned to bond1. FRER push is configured on both veth_a0 and veth_b0 egress with cross-mirroring so every frame sent by either bond slave carries an R-TAG and a mirrored copy reaches the peer slave. Tests 1-4 exercise shared and individual recover modes on the listener side. Test 5 uses a self-contained single-path (no bond) topology: ns_p2p_src ns_p2p_dst +----------------------+ +----------------------+ | frer_p2p_a0 (IP_P2P_SRC)| <---> | frer_p2p_a1 (IP_P2P_DST)| | egress: frer push | | ingress: frer recover | +----------------------+ +----------------------+ Test 6 uses a four-namespace relay topology: ns_talker -- bridge0 (br_r0) -+- path A -+- bridge1 (br_r1) -- ns_listener \- path B -/ bridge0 acts as sequence generator (frer push + replicate to both redundant paths); bridge1 acts as eliminator (frer shared recover with tag-pop on both ingress ports). Six functional test cases are included: 1. push verify - confirm that the frer push action inserts an R-TAG (EtherType 0xF1C1) on egress; tcpdump on both veth_a1 and veth_b1 must capture at least one R-TAG frame each. 2. shared recover e2e - veth_a1 and veth_b1 share one recover action; the action passes exactly one copy and discards the duplicate; verified via ping success, tcpdump frame count on bond1, and tc stats (passed >=3D PING_COUNT, discarded >=3D PING_COUNT). 3. individual recover - veth_a1 and veth_b1 use independent recover actions so both copies are passed without cross-port deduplication; verified via per-slave tcpdump and tc stats (discarded =3D 0 on each port). 4. no tag-pop - shared recover without tag-pop leaves the R-TAG on passed frames; verified by capturing EtherType 0xF1C1 (expect >=3D 1) and plain ICMP (expect 0) on bond1. 5. simple point-to-point - single-path push + individual recover (with tag-pop) end-to-end ping test; no bond. 6. relay e2e - four-namespace bridge relay topology; bridg= e0 pushes R-TAG and replicates to two paths; bridge1 recovers (shared, tag-pop) and forwards deduplicated frames to listener; verified via ping success, tcpdump frame count on listener, and bridge1 tc stats. The script conforms to the kselftest framework (TAP output, KSFT_PASS / KSFT_FAIL / KSFT_SKIP exit codes). It loads kselftest/lib.sh when available and falls back to a minimal inline implementation otherwise. All tests are skipped gracefully when act_frer is not available in the running kernel. Signed-off-by: Xiaoliang Yang --- tools/testing/selftests/net/Makefile | 1 + tools/testing/selftests/net/frer_test.sh | 1013 ++++++++++++++++++++++ 2 files changed, 1014 insertions(+) create mode 100755 tools/testing/selftests/net/frer_test.sh diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests= /net/Makefile index 6a190a525a39..67b896611f08 100644 --- a/tools/testing/selftests/net/Makefile +++ b/tools/testing/selftests/net/Makefile @@ -38,6 +38,7 @@ TEST_PROGS :=3D \ fib_rule_tests.sh \ fib_tests.sh \ fin_ack_lat.sh \ + frer_test.sh \ fq_band_pktlimit.sh \ gre_gso.sh \ gre_ipv6_lladdr.sh \ diff --git a/tools/testing/selftests/net/frer_test.sh b/tools/testing/selft= ests/net/frer_test.sh new file mode 100755 index 000000000000..ecd88952f495 --- /dev/null +++ b/tools/testing/selftests/net/frer_test.sh @@ -0,0 +1,1013 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# Copyright 2026 NXP +# +# frer_test.sh - IEEE 802.1CB FRER tc action kselftest +# +# Topology for tests 1-4: +# +# ns_talker bond0 (veth_a0 + veth_b0) <---> bond1 (veth_a1 + veth_b1)= ns_listener +# +# IP_SRC assigned to bond0; IP_DST assigned to bond1 +# +# bond mode: balance-rr (round-robin), so frames are distributed across +# both slaves. FRER push is configured on both veth_a0 and +# veth_b0 egress with cross-mirror so every frame sent by eit= her +# slave carries an R-TAG and a mirrored copy reaches the peer. +# FRER recover: veth_a1/veth_b1 ingress, shared or individual recover pe= r test +# +# Ping runs from bond0 to bond1; tcpdump captures on bond1 (or on indivi= dual +# slave interfaces for tests where both copies must be observable). +# +# Test 5: simple point-to-point, self-contained topology (no bond). +# Test 6: relay system, self-contained topology. +# +# All namespaces, veth pairs, bond interfaces, tc rules and addresses are +# created and destroyed within this script. External dependencies: +# - kernel with CONFIG_NET_ACT_FRER and CONFIG_BONDING +# - iproute2 tc with frer action support +# - tcpdump, ping +# - root privileges + +# ------------------------------------------------------------------------= ---- +# kselftest library: TAP output + exit-code constants +# ------------------------------------------------------------------------= ---- +ksft_lib=3D"${KSFT_LIB:-$(dirname "$0")/../kselftest/lib.sh}" +if [ -f "$ksft_lib" ]; then + # shellcheck source=3D/dev/null + . "$ksft_lib" +else + # Minimal fallback when run outside the kselftest tree + KSFT_PASS=3D0 + KSFT_FAIL=3D1 + KSFT_SKIP=3D4 + _ksft_count=3D0 + _ksft_pass=3D0 + _ksft_fail=3D0 + _ksft_skip=3D0 + + ksft_print_header() { echo "TAP version 13"; } + ksft_set_plan() { echo "1..$1"; } + ksft_test_result_pass() { + _ksft_count=3D$((_ksft_count + 1)); _ksft_pass=3D$((_ksft_pass + 1)) + echo "ok $_ksft_count - $*" + } + ksft_test_result_fail() { + _ksft_count=3D$((_ksft_count + 1)); _ksft_fail=3D$((_ksft_fail + 1)) + echo "not ok $_ksft_count - $*" + } + ksft_test_result_skip() { + _ksft_count=3D$((_ksft_count + 1)); _ksft_skip=3D$((_ksft_skip + 1)) + echo "ok $_ksft_count - $* # SKIP" + } + ksft_print_cnts() { + echo "# Totals: pass=3D$_ksft_pass fail=3D$_ksft_fail skip=3D$_ksft_skip" + } + ksft_exit_pass() { exit $KSFT_PASS; } + ksft_exit_fail() { exit $KSFT_FAIL; } + ksft_exit_fail_msg() { echo "# FATAL: $*" >&2; exit $KSFT_FAIL; } +fi + +# ------------------------------------------------------------------------= ---- +# Configuration (override via environment) +# ------------------------------------------------------------------------= ---- +TC=3D"${TC:-tc}" +PING=3D"${PING:-ping}" +TCPDUMP=3D"${TCPDUMP:-tcpdump}" +PING_COUNT=3D"${PING_COUNT:-5}" +PING_TIMEOUT=3D"${PING_TIMEOUT:-2}" +SKIP_MODPROBE=3D"${SKIP_MODPROBE:-0}" + +# Bond topology interfaces (tests 1-4) +readonly VETH_A0=3D"frer_a0" +readonly VETH_A1=3D"frer_a1" +readonly VETH_B0=3D"frer_b0" +readonly VETH_B1=3D"frer_b1" +readonly BOND0=3D"frer_bond0" +readonly BOND1=3D"frer_bond1" + +readonly NS_TALKER=3D"frer_ns_talker" +readonly NS_LISTENER=3D"frer_ns_listener" + +readonly IP_SRC=3D"10.0.0.1" +readonly IP_DST=3D"10.0.0.2" + +# Point-to-point topology interfaces (test 5) +readonly P2P_NS_SRC=3D"frer_p2p_src" +readonly P2P_NS_DST=3D"frer_p2p_dst" +readonly P2P_VETH_A0=3D"frer_p2p_a0" +readonly P2P_VETH_A1=3D"frer_p2p_a1" +readonly IP_P2P_SRC=3D"10.0.1.1" +readonly IP_P2P_DST=3D"10.0.1.2" + +# Relay topology interfaces (test 6) +# +# ns_talker (talker_eth.100) -- talker_eth/br0_uplink -- bridge0 (br_r0) +# |-- br0_swp0/br1_swp0 --\ +# \-- br0_swp1/br1_swp1 --+--\ +# bridge1 (br_r1) -- br1_downlink/listener_eth -- ns_listener +# +# bridge0 acts as sequence generator (frer push + replicate to both paths). +# bridge1 acts as eliminator (frer recover, shared, tag-pop). +readonly R_NS_TALKER=3D"frer_r_talker" +readonly R_NS_BRIDGE0=3D"frer_r_bridge0" +readonly R_NS_BRIDGE1=3D"frer_r_bridge1" +readonly R_NS_LISTENER=3D"frer_r_listener" +readonly R_TALKER_ETH=3D"r_tlk_eth" # talker-side physical port +readonly R_BR0_UPLINK=3D"r_br0_uplink" # bridge0 uplink facing talker +readonly R_BR0_SWP0=3D"r_br0_swp0" # bridge0 redundant path port 0 +readonly R_BR0_SWP1=3D"r_br0_swp1" # bridge0 redundant path port 1 +readonly R_BR1_SWP0=3D"r_br1_swp0" # bridge1 redundant path port 0 +readonly R_BR1_SWP1=3D"r_br1_swp1" # bridge1 redundant path port 1 +readonly R_BR1_DOWNLINK=3D"r_br1_dwnlnk" # bridge1 downlink facing listen= er +readonly R_LISTENER_ETH=3D"r_lst_eth" # listener-side physical port +readonly R_BR0=3D"br_r0" +readonly R_BR1=3D"br_r1" +readonly R_VLAN=3D100 +readonly R_IP_TALKER=3D"10.1.0.1" +readonly R_IP_LISTENER=3D"10.1.0.2" + +# FRER action index constants +readonly IDX_PUSH=3D1 +readonly IDX_SHARED_RCVY=3D10 +readonly IDX_INDV_RCVY_A=3D20 +readonly IDX_INDV_RCVY_B=3D21 +readonly IDX_NO_POP=3D30 +readonly IDX_P2P_RCVY=3D40 +readonly IDX_RELAY_PUSH=3D50 +readonly IDX_RELAY_RCVY=3D60 + +readonly NUM_TESTS=3D6 + +# ------------------------------------------------------------------------= ---- +# Prerequisite check +# ------------------------------------------------------------------------= ---- +check_prerequisites() +{ + local missing=3D0 + + [ "$(id -u)" -eq 0 ] || { echo "# Must be run as root" >&2; missing=3D1; } + + for cmd in ip "$TC" "$TCPDUMP" "$PING"; do + command -v "$cmd" >/dev/null 2>&1 || { + echo "# Missing command: $cmd" >&2 + missing=3D1 + } + done + + if [ "$missing" -ne 0 ]; then + ksft_set_plan "$NUM_TESTS" + for i in $(seq 1 "$NUM_TESTS"); do + ksft_test_result_skip "prerequisites not met (test $i)" + done + ksft_print_cnts + exit "$KSFT_SKIP" + fi +} + +load_module() +{ + [ "$SKIP_MODPROBE" =3D "1" ] && return + if ! modprobe act_frer 2>/dev/null; then + echo "# modprobe act_frer failed - may be built-in or unavailable" >&2 + fi + if ! modprobe bonding 2>/dev/null; then + echo "# modprobe bonding failed - may be built-in or unavailable" >&2 + fi +} + +check_frer_action() +{ + ip netns exec "$NS_TALKER" \ + $TC actions add action frer push index 999 2>/dev/null || return 1 + ip netns exec "$NS_TALKER" \ + $TC actions del action frer index 999 2>/dev/null || true + return 0 +} + +# ------------------------------------------------------------------------= ---- +# Bond topology setup / teardown (used by tests 1-4) +# ------------------------------------------------------------------------= ---- +setup_topology() +{ + for n in "$NS_TALKER" "$NS_LISTENER"; do + ip netns add "$n" + done + + ip link add "$VETH_A0" type veth peer name "$VETH_A1" + ip link set "$VETH_A0" netns "$NS_TALKER" + ip link set "$VETH_A1" netns "$NS_LISTENER" + + ip link add "$VETH_B0" type veth peer name "$VETH_B1" + ip link set "$VETH_B0" netns "$NS_TALKER" + ip link set "$VETH_B1" netns "$NS_LISTENER" + + # ns_talker: create bond0 (balance-rr), frames round-robin across both sl= aves. + ip netns exec "$NS_TALKER" ip link set lo up + ip netns exec "$NS_TALKER" ip link add "$BOND0" type bond mode balance-rr= miimon 100 + ip netns exec "$NS_TALKER" ip link set "$VETH_A0" master "$BOND0" + ip netns exec "$NS_TALKER" ip link set "$VETH_B0" master "$BOND0" + ip netns exec "$NS_TALKER" ip link set "$VETH_A0" up + ip netns exec "$NS_TALKER" ip link set "$VETH_B0" up + ip netns exec "$NS_TALKER" ip link set "$BOND0" up + ip netns exec "$NS_TALKER" ip addr add "${IP_SRC}/24" dev "$BOND0" + + # ns_listener: create bond1 (balance-rr). + ip netns exec "$NS_LISTENER" ip link set lo up + ip netns exec "$NS_LISTENER" ip link add "$BOND1" type bond mode balance-= rr miimon 100 + ip netns exec "$NS_LISTENER" ip link set "$VETH_A1" master "$BOND1" + ip netns exec "$NS_LISTENER" ip link set "$VETH_B1" master "$BOND1" + ip netns exec "$NS_LISTENER" ip link set "$VETH_A1" up + ip netns exec "$NS_LISTENER" ip link set "$VETH_B1" up + ip netns exec "$NS_LISTENER" ip link set "$BOND1" up + ip netns exec "$NS_LISTENER" ip addr add "${IP_DST}/24" dev "$BOND1" + + # Static ARP so L2 forwarding works without ARP broadcasts. + # With balance-rr both slaves share the bond MAC. + local mac_bond0 mac_bond1 + mac_bond0=3D$(ip netns exec "$NS_TALKER" cat /sys/class/net/"$BOND0"/ad= dress) + mac_bond1=3D$(ip netns exec "$NS_LISTENER" cat /sys/class/net/"$BOND1"/ad= dress) + ip netns exec "$NS_TALKER" ip neigh add "$IP_DST" lladdr "$mac_bond1" d= ev "$BOND0" + ip netns exec "$NS_LISTENER" ip neigh add "$IP_SRC" lladdr "$mac_bond0" d= ev "$BOND1" +} + +cleanup() +{ + for n in "$NS_TALKER" "$NS_LISTENER" \ + "$P2P_NS_SRC" "$P2P_NS_DST" \ + "$R_NS_TALKER" "$R_NS_BRIDGE0" "$R_NS_BRIDGE1" "$R_NS_LISTENER"; do + ip netns del "$n" 2>/dev/null || true + done +} +trap cleanup EXIT + +# ------------------------------------------------------------------------= ---- +# TC rule helpers +# ------------------------------------------------------------------------= ---- + +# Push on both veth_a0 and veth_b0 egress using the same shared frer push +# action (IDX_PUSH). Each slave also mirrors to the other so that every +# outgoing frame is replicated onto both paths regardless of which slave t= he +# bond currently selects. This prevents packet loss during bond link chan= ges. +setup_push_mirror() +{ + ip netns exec "$NS_TALKER" $TC qdisc add dev "$VETH_A0" clsact + ip netns exec "$NS_TALKER" $TC filter add dev "$VETH_A0" egress \ + protocol ip flower skip_hw \ + action frer push index $IDX_PUSH \ + action mirred egress mirror dev "$VETH_B0" + + ip netns exec "$NS_TALKER" $TC qdisc add dev "$VETH_B0" clsact + ip netns exec "$NS_TALKER" $TC filter add dev "$VETH_B0" egress \ + protocol ip flower skip_hw \ + action frer push index $IDX_PUSH \ + action mirred egress mirror dev "$VETH_A0" +} + +teardown_tc() +{ + for dev in "$VETH_A0" "$VETH_B0"; do + ip netns exec "$NS_TALKER" $TC qdisc del dev "$dev" clsact \ + 2>/dev/null || true + done + for dev in "$VETH_A1" "$VETH_B1"; do + ip netns exec "$NS_LISTENER" $TC qdisc del dev "$dev" clsact \ + 2>/dev/null || true + done + ip netns exec "$NS_TALKER" $TC actions flush action frer 2>/dev/null ||= true + ip netns exec "$NS_LISTENER" $TC actions flush action frer 2>/dev/null ||= true +} + +# ------------------------------------------------------------------------= ---- +# Packet-capture helpers +# +# capture_start_on NS IFACE PCAP [BPF_FILTER] +# Starts tcpdump in namespace NS on IFACE, writing to PCAP. +# Stores PID in _CAP_PID. +# +# capture_stop +# Waits for tcpdump (stored in _CAP_PID) to finish. +# +# capture_count_on NS PCAP +# Prints the number of captured packets. +# +# Convenience wrappers capture_start / capture_count target bond1 in +# NS_LISTENER (the primary observation point for tests 2 and 4). +# ------------------------------------------------------------------------= ---- +_CAP_PID=3D"" + +capture_start_on() +{ + local ns=3D"$1" iface=3D"$2" pcap=3D"$3" filter=3D"${4:-}" + + if [ -n "$filter" ]; then + ip netns exec "$ns" timeout 4 \ + $TCPDUMP -i "$iface" -w "$pcap" \ + --immediate-mode -Z root -y EN10MB \ + $filter >/dev/null 2>&1 & + else + ip netns exec "$ns" timeout 4 \ + $TCPDUMP -i "$iface" -w "$pcap" \ + --immediate-mode -Z root -y EN10MB \ + >/dev/null 2>&1 & + fi + _CAP_PID=3D$! + + # Wait until tcpdump opens a packet socket (max ~2.5 s). + local tries=3D0 + while [ $tries -lt 50 ]; do + ip netns exec "$ns" grep -q "$iface" /proc/net/packet 2>/dev/null && bre= ak + sleep 0.05 + tries=3D$((tries + 1)) + done +} + +capture_stop() +{ + [ -n "$_CAP_PID" ] || return 0 + wait "$_CAP_PID" 2>/dev/null || true + _CAP_PID=3D"" +} + +capture_count_on() +{ + local ns=3D"$1" pcap=3D"$2" + ip netns exec "$ns" \ + $TCPDUMP -r "$pcap" --no-promiscuous-mode 2>/dev/null \ + | grep -c "^[0-9]" || true +} + +# Convenience wrappers: default to bond1 in NS_LISTENER +capture_start() { capture_start_on "$NS_LISTENER" "$BOND1" "$@"; } +capture_count() { capture_count_on "$NS_LISTENER" "$1"; } + +# ------------------------------------------------------------------------= ---- +# Ping helper +# ------------------------------------------------------------------------= ---- +do_ping() +{ + local rc=3D0 + ip netns exec "$NS_TALKER" \ + $PING -c "$PING_COUNT" -W "$PING_TIMEOUT" -i 0.2 -q \ + "$IP_DST" >/dev/null 2>&1 || rc=3D$? + return $rc +} + +# ------------------------------------------------------------------------= ---- +# tc statistics parser +# ------------------------------------------------------------------------= ---- +tc_stat() +{ + local dump=3D"$1" field=3D"$2" + echo "$dump" | awk -F"${field}=3D" 'NF>1{split($2,a," ");print a[1];exit}= ' || echo "0" +} + +# ------------------------------------------------------------------------= ---- +# TEST 1: PUSH VERIFY (bond topology) +# +# Only push is configured on the talker side; no recover on the listener. +# The push action on veth_a0 egress inserts an R-TAG and mirrors a copy to +# veth_b0, so both listener slaves (veth_a1 and veth_b1) receive a frame +# with EtherType 0xF1C1. Captures run sequentially on each slave to verify +# that both paths carry R-TAG frames. +# +# Pass criteria: +# - veth_a1 captures >=3D 1 R-TAG frame +# - veth_b1 captures >=3D 1 R-TAG frame +# ------------------------------------------------------------------------= ---- +test_push_verify_bond() +{ + local pcap_a pcap_b cap_a cap_b + local result=3D"pass" + + setup_push_mirror + + # Capture 1: R-TAG frames on veth_a1 (path A) + pcap_a=3D$(mktemp /tmp/frer_bond_push_a_XXXXXX.pcap) + capture_start_on "$NS_LISTENER" "$VETH_A1" "$pcap_a" "ether proto 0xf1c1" + ip netns exec "$NS_TALKER" \ + $PING -c 3 -W 1 -i 0.2 -q "$IP_DST" >/dev/null 2>&1 || true + capture_stop + cap_a=3D$(capture_count_on "$NS_LISTENER" "$pcap_a") + rm -f "$pcap_a" + + # Capture 2: R-TAG frames on veth_b1 (path B, mirrored copy) + pcap_b=3D$(mktemp /tmp/frer_bond_push_b_XXXXXX.pcap) + capture_start_on "$NS_LISTENER" "$VETH_B1" "$pcap_b" "ether proto 0xf1c1" + ip netns exec "$NS_TALKER" \ + $PING -c 3 -W 1 -i 0.2 -q "$IP_DST" >/dev/null 2>&1 || true + capture_stop + cap_b=3D$(capture_count_on "$NS_LISTENER" "$pcap_b") + rm -f "$pcap_b" + + teardown_tc + + echo "# bond push verify: veth_a1 R-TAG=3D$cap_a veth_b1 R-TAG=3D$cap_b" + + [ "$cap_a" -ge 1 ] || result=3D"fail" + [ "$cap_b" -ge 1 ] || result=3D"fail" + + if [ "$result" =3D "pass" ]; then + ksft_test_result_pass \ + "bond push verify: R-TAG on both paths (a1=3D$cap_a b1=3D$cap_b)" + else + ksft_test_result_fail \ + "bond push verify: expected R-TAG on both paths (a1=3D$cap_a b1=3D$cap_= b)" + fi +} + +# ------------------------------------------------------------------------= ---- +# TEST 2: SHARED RECOVER E2E (bond topology) +# +# veth_a1 and veth_b1 ingress share one recover action (idx=3D10) with tag= -pop. +# The listener receives two R-TAG copies per request; the shared recover p= asses +# exactly one and discards the other. The recovered plain ICMP reaches bo= nd1's +# IP stack and a reply is sent, making ping succeed. +# +# Pass criteria: +# - ping succeeds (rc=3D0) +# - tcpdump on bond1 captures exactly PING_COUNT ICMP echo-request frames +# (filter is restricted to type=3D8 to exclude echo replies, which wou= ld +# double the count since bond1 also originates the reply packets) +# - tc stats on veth_a1: passed >=3D PING_COUNT, discarded >=3D PING_COU= NT +# ------------------------------------------------------------------------= ---- +test_shared_recover_bond() +{ + local pcap cap_count ping_rc=3D0 + local dump_a + local total_passed total_discarded tagless + local result=3D"pass" + + setup_push_mirror + + # veth_a1 ingress: create shared recover action with tag-pop + ip netns exec "$NS_LISTENER" $TC qdisc add dev "$VETH_A1" clsact + ip netns exec "$NS_LISTENER" $TC filter add dev "$VETH_A1" ingress \ + protocol all flower skip_hw \ + action frer recover alg vector history-length 16 \ + reset-time 2000 tag-pop index $IDX_SHARED_RCVY + + # veth_b1 ingress: bind to the same shared action by index + ip netns exec "$NS_LISTENER" $TC qdisc add dev "$VETH_B1" clsact + ip netns exec "$NS_LISTENER" $TC filter add dev "$VETH_B1" ingress \ + protocol all flower skip_hw \ + action frer recover index $IDX_SHARED_RCVY + + pcap=3D$(mktemp /tmp/frer_bond_shared_XXXXXX.pcap) + capture_start "$pcap" "icmp[icmptype] =3D=3D icmp-echo" + + do_ping || ping_rc=3D$? + + capture_stop + + cap_count=3D$(capture_count "$pcap") + rm -f "$pcap" + + dump_a=3D$(ip netns exec "$NS_LISTENER" \ + $TC -s filter show dev "$VETH_A1" ingress 2>/dev/null) + + teardown_tc + + total_passed=3D$(tc_stat "$dump_a" "passed") + total_discarded=3D$(tc_stat "$dump_a" "discarded") + tagless=3D$(tc_stat "$dump_a" "tagless") + total_discarded=3D$((total_discarded - tagless)) + + echo "# bond shared recover: ping_rc=3D$ping_rc cap=3D$cap_count" \ + "passed=3D$total_passed discarded=3D$total_discarded" + + [ "$ping_rc" -eq 0 ] || result=3D"fail" + [ "$cap_count" -eq "$PING_COUNT" ] || result=3D"fail" + [ "$total_passed" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$total_discarded" -ge "$PING_COUNT" ] || result=3D"fail" + + if [ "$result" =3D "pass" ]; then + ksft_test_result_pass \ + "bond shared recover: ping OK, cap=3D$cap_count" \ + "passed=3D$total_passed discarded=3D$total_discarded" + else + ksft_test_result_fail \ + "bond shared recover: ping_rc=3D$ping_rc cap=3D$cap_count" \ + "passed=3D$total_passed discarded=3D$total_discarded" \ + "(expected ping OK, cap=3D$PING_COUNT," \ + "passed>=3D$PING_COUNT, discarded>=3D$PING_COUNT)" + fi +} + +# ------------------------------------------------------------------------= ---- +# TEST 3: INDIVIDUAL RECOVER (bond topology) +# +# veth_a1 and veth_b1 use independent recover actions (idx=3D20 and idx=3D= 21). +# Each port maintains its own sequence history so both copies of every fra= me +# are passed (no cross-port deduplication). With active-backup bond1, only +# the active slave's (veth_a1) recovered frame reaches bond1's IP stack, so +# ping succeeds. The absence of deduplication is verified via per-slave +# tcpdump (each slave should capture PING_COUNT ICMP frames) and tc stats. +# +# Pass criteria: +# - ping succeeds +# - veth_a1 captures PING_COUNT ICMP frames (passed, not discarded) +# - veth_b1 captures PING_COUNT ICMP frames (passed independently) +# - tc stats: veth_a1 passed=3DPING_COUNT discarded=3D0 +# veth_b1 passed=3DPING_COUNT discarded=3D0 +# ------------------------------------------------------------------------= ---- +test_individual_recover_bond() +{ + local pcap_a pcap_b cap_a cap_b ping_rc=3D0 + local dump_a dump_b + local passed_a discarded_a passed_b discarded_b tagless_a tagless_b + local result=3D"pass" + + setup_push_mirror + + # veth_a1 ingress: individual recover idx=3D20 (independent state) + ip netns exec "$NS_LISTENER" $TC qdisc add dev "$VETH_A1" clsact + ip netns exec "$NS_LISTENER" $TC filter add dev "$VETH_A1" ingress \ + protocol all flower skip_hw \ + action frer recover individual alg vector history-length 16 \ + reset-time 2000 tag-pop index $IDX_INDV_RCVY_A + + # veth_b1 ingress: individual recover idx=3D21 (separate independent stat= e) + ip netns exec "$NS_LISTENER" $TC qdisc add dev "$VETH_B1" clsact + ip netns exec "$NS_LISTENER" $TC filter add dev "$VETH_B1" ingress \ + protocol all flower skip_hw \ + action frer recover individual alg vector history-length 16 \ + reset-time 2000 tag-pop index $IDX_INDV_RCVY_B + + # Per-slave capture A: verify veth_a1 passes frames; also use this run + # for the overall ping_rc check (do_ping targets bond0->bond1). + pcap_a=3D$(mktemp /tmp/frer_bond_indv_a_XXXXXX.pcap) + capture_start_on "$NS_LISTENER" "$VETH_A1" "$pcap_a" "icmp" + do_ping || ping_rc=3D$? + capture_stop + cap_a=3D$(capture_count_on "$NS_LISTENER" "$pcap_a") + rm -f "$pcap_a" + + # Per-slave capture B: verify veth_b1 also passes frames (balance-rr + # distributes egress across both slaves, so both paths carry traffic). + pcap_b=3D$(mktemp /tmp/frer_bond_indv_b_XXXXXX.pcap) + capture_start_on "$NS_LISTENER" "$VETH_B1" "$pcap_b" "icmp" + do_ping || true + capture_stop + cap_b=3D$(capture_count_on "$NS_LISTENER" "$pcap_b") + rm -f "$pcap_b" + + dump_a=3D$(ip netns exec "$NS_LISTENER" \ + $TC -s filter show dev "$VETH_A1" ingress 2>/dev/null) + dump_b=3D$(ip netns exec "$NS_LISTENER" \ + $TC -s filter show dev "$VETH_B1" ingress 2>/dev/null) + + teardown_tc + + passed_a=3D$(tc_stat "$dump_a" "passed") + discarded_a=3D$(tc_stat "$dump_a" "discarded") + tagless_a=3D$(tc_stat "$dump_a" "tagless") + passed_b=3D$(tc_stat "$dump_b" "passed") + discarded_b=3D$(tc_stat "$dump_b" "discarded") + tagless_b=3D$(tc_stat "$dump_b" "tagless") + discarded_a=3D$((discarded_a - tagless_a)) + discarded_b=3D$((discarded_b - tagless_b)) + + echo "# bond individual recover: ping_rc=3D$ping_rc" \ + "a1: cap=3D$cap_a passed=3D$passed_a discarded=3D$discarded_a" \ + "b1: cap=3D$cap_b passed=3D$passed_b discarded=3D$discarded_b" + + [ "$ping_rc" -eq 0 ] || result=3D"fail" + [ "$cap_a" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$cap_b" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$passed_a" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$passed_b" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$discarded_a" -eq 0 ] || result=3D"fail" + [ "$discarded_b" -eq 0 ] || result=3D"fail" + + if [ "$result" =3D "pass" ]; then + ksft_test_result_pass \ + "bond individual recover: ping OK" \ + "a1: cap=3D$cap_a passed=3D$passed_a/0" \ + "b1: cap=3D$cap_b passed=3D$passed_b/0" + else + ksft_test_result_fail \ + "bond individual recover: ping_rc=3D$ping_rc" \ + "a1: cap=3D$cap_a passed=3D$passed_a discarded=3D$discarded_a" \ + "b1: cap=3D$cap_b passed=3D$passed_b discarded=3D$discarded_b" + fi +} + +# ------------------------------------------------------------------------= ---- +# TEST 4: NO TAG-POP (bond topology) +# +# Shared recover runs without tag-pop; passed frames still carry the R-TAG +# when they reach bond1. +# +# Pass criteria: +# - tcpdump on bond1 with "ether proto 0xf1c1" captures >=3D 1 R-TAG fra= me +# - tcpdump on bond1 with "icmp" captures 0 frames (outer EtherType is +# 0xF1C1, not 0x0800, so plain-IP ICMP filter does not match) +# ------------------------------------------------------------------------= ---- +test_no_tag_pop_bond() +{ + local pcap_rtag pcap_icmp rtag_count icmp_count + local result=3D"pass" + + setup_push_mirror + + # veth_a1 ingress: shared recover WITHOUT tag-pop + ip netns exec "$NS_LISTENER" $TC qdisc add dev "$VETH_A1" clsact + ip netns exec "$NS_LISTENER" $TC filter add dev "$VETH_A1" ingress \ + protocol all flower skip_hw \ + action frer recover alg vector history-length 16 \ + reset-time 2000 index $IDX_NO_POP + + # veth_b1 ingress: bind to the same shared action + ip netns exec "$NS_LISTENER" $TC qdisc add dev "$VETH_B1" clsact + ip netns exec "$NS_LISTENER" $TC filter add dev "$VETH_B1" ingress \ + protocol all flower skip_hw \ + action frer recover index $IDX_NO_POP + + # Capture 1: frames with R-TAG EtherType on bond1 (expect >=3D 1) + pcap_rtag=3D$(mktemp /tmp/frer_bond_nopop_rtag_XXXXXX.pcap) + capture_start "$pcap_rtag" "ether proto 0xf1c1" + ip netns exec "$NS_TALKER" \ + $PING -c 3 -W 1 -i 0.2 -q "$IP_DST" >/dev/null 2>&1 || true + capture_stop + rtag_count=3D$(capture_count "$pcap_rtag") + rm -f "$pcap_rtag" + + # Capture 2: plain ICMP frames on bond1 (expect 0) + pcap_icmp=3D$(mktemp /tmp/frer_bond_nopop_icmp_XXXXXX.pcap) + capture_start "$pcap_icmp" "icmp" + ip netns exec "$NS_TALKER" \ + $PING -c 3 -W 1 -i 0.2 -q "$IP_DST" >/dev/null 2>&1 || true + capture_stop + icmp_count=3D$(capture_count "$pcap_icmp") + rm -f "$pcap_icmp" + + teardown_tc + + echo "# bond no tag-pop: rtag=3D$rtag_count (expected >=3D1) icmp=3D$icmp= _count (expected 0)" + + [ "$rtag_count" -ge 1 ] || result=3D"fail" + [ "$icmp_count" -eq 0 ] || result=3D"fail" + + if [ "$result" =3D "pass" ]; then + ksft_test_result_pass \ + "bond no tag-pop: R-TAG present on bond1 " \ + "(rtag=3D$rtag_count), ICMP absent (icmp=3D$icmp_count)" + else + ksft_test_result_fail \ + "bond no tag-pop: rtag=3D$rtag_count icmp=3D$icmp_count " \ + "(expected rtag>=3D1 icmp=3D0)" + fi +} + +# ------------------------------------------------------------------------= ---- +# TEST 5: SIMPLE POINT-TO-POINT (no bond) +# +# Self-contained single-path topology: push on p2p_a0 egress, individual +# recover (with tag-pop) on p2p_a1 ingress. IP is assigned directly to the +# veth interfaces (no bond). +# +# Pass criteria: +# - ping succeeds (rc=3D0) +# - veth_a1 recover stats: passed >=3D PING_COUNT, discarded =3D 0 +# ------------------------------------------------------------------------= ---- +test_simple_point_to_point() +{ + local ping_rc=3D0 + local dump_a1 passed discarded + local result=3D"pass" + + # Create self-contained p2p namespaces + ip netns add "$P2P_NS_SRC" + ip netns add "$P2P_NS_DST" + + ip link add "$P2P_VETH_A0" type veth peer name "$P2P_VETH_A1" + ip link set "$P2P_VETH_A0" netns "$P2P_NS_SRC" + ip link set "$P2P_VETH_A1" netns "$P2P_NS_DST" + + ip netns exec "$P2P_NS_SRC" ip link set lo up + ip netns exec "$P2P_NS_SRC" ip link set "$P2P_VETH_A0" up + ip netns exec "$P2P_NS_SRC" ip addr add "${IP_P2P_SRC}/24" dev "$P2P_VETH= _A0" + + ip netns exec "$P2P_NS_DST" ip link set lo up + ip netns exec "$P2P_NS_DST" ip link set "$P2P_VETH_A1" up + ip netns exec "$P2P_NS_DST" ip addr add "${IP_P2P_DST}/24" dev "$P2P_VETH= _A1" + + local mac_a0 mac_a1 + mac_a0=3D$(ip netns exec "$P2P_NS_SRC" cat /sys/class/net/"$P2P_VETH_A0"/= address) + mac_a1=3D$(ip netns exec "$P2P_NS_DST" cat /sys/class/net/"$P2P_VETH_A1"/= address) + ip netns exec "$P2P_NS_SRC" ip neigh add "$IP_P2P_DST" lladdr "$mac_a1" d= ev "$P2P_VETH_A0" + ip netns exec "$P2P_NS_DST" ip neigh add "$IP_P2P_SRC" lladdr "$mac_a0" d= ev "$P2P_VETH_A1" + + # veth_a0 egress: push R-TAG + ip netns exec "$P2P_NS_SRC" $TC qdisc add dev "$P2P_VETH_A0" clsact + ip netns exec "$P2P_NS_SRC" $TC filter add dev "$P2P_VETH_A0" egress \ + protocol ip flower skip_hw \ + action frer push index $IDX_PUSH + + # veth_a1 ingress: individual recover with tag-pop + ip netns exec "$P2P_NS_DST" $TC qdisc add dev "$P2P_VETH_A1" clsact + ip netns exec "$P2P_NS_DST" $TC filter add dev "$P2P_VETH_A1" ingress \ + protocol all flower skip_hw \ + action frer recover individual alg vector history-length 16 \ + reset-time 2000 tag-pop index $IDX_P2P_RCVY + + ip netns exec "$P2P_NS_SRC" \ + $PING -c "$PING_COUNT" -W "$PING_TIMEOUT" -i 0.2 -q \ + "$IP_P2P_DST" >/dev/null 2>&1 || ping_rc=3D$? + + dump_a1=3D$(ip netns exec "$P2P_NS_DST" \ + $TC -s filter show dev "$P2P_VETH_A1" ingress 2>/dev/null) + + # Teardown p2p topology + for dev in "$P2P_VETH_A0"; do + ip netns exec "$P2P_NS_SRC" $TC qdisc del dev "$dev" clsact \ + 2>/dev/null || true + done + for dev in "$P2P_VETH_A1"; do + ip netns exec "$P2P_NS_DST" $TC qdisc del dev "$dev" clsact \ + 2>/dev/null || true + done + ip netns exec "$P2P_NS_SRC" $TC actions flush action frer 2>/dev/null || = true + ip netns exec "$P2P_NS_DST" $TC actions flush action frer 2>/dev/null || = true + ip netns del "$P2P_NS_SRC" 2>/dev/null || true + ip netns del "$P2P_NS_DST" 2>/dev/null || true + + passed=3D$(tc_stat "$dump_a1" "passed") + discarded=3D$(tc_stat "$dump_a1" "discarded") + local tagless + tagless=3D$(tc_stat "$dump_a1" "tagless") + discarded=3D$((discarded - tagless)) + + echo "# p2p: ping_rc=3D$ping_rc passed=3D$passed discarded=3D$discarded" + + [ "$ping_rc" -eq 0 ] || result=3D"fail" + [ "$passed" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$discarded" -eq 0 ] || result=3D"fail" + + if [ "$result" =3D "pass" ]; then + ksft_test_result_pass \ + "simple p2p: ping OK, passed=3D$passed discarded=3D$discarded" + else + ksft_test_result_fail \ + "simple p2p: ping_rc=3D$ping_rc passed=3D$passed discarded=3D$discarded" + fi +} + +# ------------------------------------------------------------------------= ---- +# TEST 6: RELAY E2E (self-contained, no bond) +# +# Talker sends VLAN-100 frames into bridge0 (sequence generator). Bridge0 +# pushes an R-TAG and replicates to two redundant paths. Bridge1 (elimina= tor) +# recovers (shared, tag-pop) on both paths and forwards the deduplicated f= rame +# to the listener. +# +# Topology: +# ns_talker (talker_eth.100) -- talker_eth/br0_uplink +# -- bridge0 (br_r0) -+- br0_swp0/br1_swp0 -+ +# \- br0_swp1/br1_swp1 -+ +# -- bridge1 (br_r1) -- br1_downlink/listener_eth -- ns_listener +# +# FRER rules: +# bridge0 / br0_uplink ingress : push idx=3D50, redirect br0_swp0, mirr= or br0_swp1 +# bridge1 / br1_swp0 ingress : recover (shared, tag-pop) idx=3D60, re= direct br1_downlink +# bridge1 / br1_swp1 ingress : recover idx=3D60 (bind same), redirect= br1_downlink +# bridge1 / br1_downlink ingress: redirect br1_swp0 (reply path, bypass = FDB) +# +# Pass criteria: +# - ping from ns_talker to ns_listener succeeds (rc=3D0) +# - tcpdump on listener captures exactly PING_COUNT ICMP echo-request fr= ames +# - br1_swp0 tc stats: passed >=3D PING_COUNT, discarded >=3D PING_COUNT +# ------------------------------------------------------------------------= ---- +teardown_relay_tc() +{ + for dev in "$R_BR0_UPLINK"; do + ip netns exec "$R_NS_BRIDGE0" $TC qdisc del dev "$dev" clsact \ + 2>/dev/null || true + done + for dev in "$R_BR1_SWP0" "$R_BR1_SWP1" "$R_BR1_DOWNLINK"; do + ip netns exec "$R_NS_BRIDGE1" $TC qdisc del dev "$dev" clsact \ + 2>/dev/null || true + done + ip netns exec "$R_NS_BRIDGE0" $TC actions flush action frer 2>/dev/null |= | true + ip netns exec "$R_NS_BRIDGE1" $TC actions flush action frer 2>/dev/null |= | true +} + +test_relay_e2e() +{ + local ping_rc=3D0 + local dump_r1swp0 + local total_passed total_discarded + local result=3D"pass" + local ns + + for ns in "$R_NS_TALKER" "$R_NS_BRIDGE0" "$R_NS_BRIDGE1" "$R_NS_LISTENER"= ; do + ip netns add "$ns" || { + echo "# relay e2e: failed to create netns $ns" >&2 + ksft_test_result_skip "relay e2e: netns setup failed" + return + } + done + + ip link add "$R_TALKER_ETH" type veth peer name "$R_BR0_UPLINK" + ip link add "$R_BR0_SWP0" type veth peer name "$R_BR1_SWP0" + ip link add "$R_BR0_SWP1" type veth peer name "$R_BR1_SWP1" + ip link add "$R_BR1_DOWNLINK" type veth peer name "$R_LISTENER_ETH" + + ip link set "$R_TALKER_ETH" netns "$R_NS_TALKER" + ip link set "$R_BR0_UPLINK" netns "$R_NS_BRIDGE0" + ip link set "$R_BR0_SWP0" netns "$R_NS_BRIDGE0" + ip link set "$R_BR0_SWP1" netns "$R_NS_BRIDGE0" + ip link set "$R_BR1_SWP0" netns "$R_NS_BRIDGE1" + ip link set "$R_BR1_SWP1" netns "$R_NS_BRIDGE1" + ip link set "$R_BR1_DOWNLINK" netns "$R_NS_BRIDGE1" + ip link set "$R_LISTENER_ETH" netns "$R_NS_LISTENER" + + local ns_dev + for ns_dev in \ + "$R_NS_TALKER:$R_TALKER_ETH" \ + "$R_NS_BRIDGE0:$R_BR0_UPLINK" "$R_NS_BRIDGE0:$R_BR0_SWP0" \ + "$R_NS_BRIDGE0:$R_BR0_SWP1" \ + "$R_NS_BRIDGE1:$R_BR1_SWP0" "$R_NS_BRIDGE1:$R_BR1_SWP1" \ + "$R_NS_BRIDGE1:$R_BR1_DOWNLINK" \ + "$R_NS_LISTENER:$R_LISTENER_ETH"; do + local _ns=3D"${ns_dev%%:*}" + local _dev=3D"${ns_dev##*:}" + ip netns exec "$_ns" ip link set lo up + ip netns exec "$_ns" ip link set "$_dev" up + done + + # bridge0: sequence generator, VLAN filtering + ip netns exec "$R_NS_BRIDGE0" ip link add name "$R_BR0" type bridge vlan_= filtering 1 + ip netns exec "$R_NS_BRIDGE0" ip link set "$R_BR0" up + ip netns exec "$R_NS_BRIDGE0" ip link set "$R_BR0_UPLINK" master "$R_BR0" + ip netns exec "$R_NS_BRIDGE0" ip link set "$R_BR0_SWP0" master "$R_BR0" + ip netns exec "$R_NS_BRIDGE0" ip link set "$R_BR0_SWP1" master "$R_BR0" + + ip netns exec "$R_NS_BRIDGE0" bridge vlan add dev "$R_BR0_UPLINK" vid "$R= _VLAN" + ip netns exec "$R_NS_BRIDGE0" bridge vlan add dev "$R_BR0_SWP0" vid "$R_V= LAN" + ip netns exec "$R_NS_BRIDGE0" bridge vlan del dev "$R_BR0_SWP1" vid 1 + ip netns exec "$R_NS_BRIDGE0" bridge vlan add dev "$R_BR0_SWP1" \ + vid "$R_VLAN" pvid untagged + ip netns exec "$R_NS_BRIDGE0" bridge link set dev "$R_BR0_SWP0" learning = off + ip netns exec "$R_NS_BRIDGE0" bridge link set dev "$R_BR0_SWP1" learning = off + ip netns exec "$R_NS_BRIDGE0" bridge vlan set dev "$R_BR0_SWP0" vid "$R_V= LAN" noflood + ip netns exec "$R_NS_BRIDGE0" bridge vlan set dev "$R_BR0_SWP1" vid "$R_V= LAN" noflood + + # bridge1: eliminator, VLAN filtering + ip netns exec "$R_NS_BRIDGE1" ip link add name "$R_BR1" type bridge vlan_= filtering 1 + ip netns exec "$R_NS_BRIDGE1" ip link set "$R_BR1" up + ip netns exec "$R_NS_BRIDGE1" ip link set "$R_BR1_SWP0" master "$R_BR1" + ip netns exec "$R_NS_BRIDGE1" ip link set "$R_BR1_SWP1" master "$R_BR1" + ip netns exec "$R_NS_BRIDGE1" ip link set "$R_BR1_DOWNLINK" master "$R_BR= 1" + + ip netns exec "$R_NS_BRIDGE1" bridge vlan add dev "$R_BR1_SWP0" vid "$R_V= LAN" + ip netns exec "$R_NS_BRIDGE1" bridge vlan del dev "$R_BR1_SWP1" vid 1 + ip netns exec "$R_NS_BRIDGE1" bridge vlan add dev "$R_BR1_SWP1" \ + vid "$R_VLAN" pvid untagged + ip netns exec "$R_NS_BRIDGE1" bridge vlan add dev "$R_BR1_DOWNLINK" vid "= $R_VLAN" + ip netns exec "$R_NS_BRIDGE1" bridge link set dev "$R_BR1_SWP0" learning = off + ip netns exec "$R_NS_BRIDGE1" bridge link set dev "$R_BR1_SWP1" learning = off + ip netns exec "$R_NS_BRIDGE1" bridge vlan set dev "$R_BR1_SWP0" vid "$R_V= LAN" noflood + ip netns exec "$R_NS_BRIDGE1" bridge vlan set dev "$R_BR1_SWP1" vid "$R_V= LAN" noflood + + # ns_talker: VLAN sub-interface + ip netns exec "$R_NS_TALKER" ip link add link "$R_TALKER_ETH" \ + name "${R_TALKER_ETH}.${R_VLAN}" type vlan id "$R_VLAN" + ip netns exec "$R_NS_TALKER" ip link set "${R_TALKER_ETH}.${R_VLAN}" up + ip netns exec "$R_NS_TALKER" ip addr add "${R_IP_TALKER}/24" \ + dev "${R_TALKER_ETH}.${R_VLAN}" + + # ns_listener: VLAN sub-interface + ip netns exec "$R_NS_LISTENER" ip link add link "$R_LISTENER_ETH" \ + name "${R_LISTENER_ETH}.${R_VLAN}" type vlan id "$R_VLAN" + ip netns exec "$R_NS_LISTENER" ip link set "${R_LISTENER_ETH}.${R_VLAN}" = up + ip netns exec "$R_NS_LISTENER" ip addr add "${R_IP_LISTENER}/24" \ + dev "${R_LISTENER_ETH}.${R_VLAN}" + + # Static ARP (VLAN 100 flooding is disabled) + local mac_talker mac_listener + mac_talker=3D$(ip netns exec "$R_NS_TALKER" \ + cat /sys/class/net/"${R_TALKER_ETH}.${R_VLAN}"/address) + mac_listener=3D$(ip netns exec "$R_NS_LISTENER" \ + cat /sys/class/net/"${R_LISTENER_ETH}.${R_VLAN}"/address) + ip netns exec "$R_NS_TALKER" ip neigh add "$R_IP_LISTENER" \ + lladdr "$mac_listener" dev "${R_TALKER_ETH}.${R_VLAN}" + ip netns exec "$R_NS_LISTENER" ip neigh add "$R_IP_TALKER" \ + lladdr "$mac_talker" dev "${R_LISTENER_ETH}.${R_VLAN}" + + # bridge0 / br0_uplink ingress: push R-TAG then replicate to both redunda= nt paths. + # mirror must come before redirect because redirect is a terminating acti= on. + ip netns exec "$R_NS_BRIDGE0" $TC qdisc add dev "$R_BR0_UPLINK" clsact + ip netns exec "$R_NS_BRIDGE0" $TC filter add dev "$R_BR0_UPLINK" ingress \ + protocol 802.1Q flower skip_hw vlan_id "$R_VLAN" \ + action frer push index $IDX_RELAY_PUSH \ + action mirred egress mirror dev "$R_BR0_SWP1" \ + action mirred egress redirect dev "$R_BR0_SWP0" + + # bridge1 / br1_swp0 ingress: create shared recover action (tag-pop) + ip netns exec "$R_NS_BRIDGE1" $TC qdisc add dev "$R_BR1_SWP0" clsact + ip netns exec "$R_NS_BRIDGE1" $TC filter add dev "$R_BR1_SWP0" ingress \ + protocol all flower skip_hw \ + action frer recover alg vector history-length 16 \ + reset-time 2000 tag-pop index $IDX_RELAY_RCVY \ + action mirred egress redirect dev "$R_BR1_DOWNLINK" + + # bridge1 / br1_swp1 ingress: bind to the same shared recover action + ip netns exec "$R_NS_BRIDGE1" $TC qdisc add dev "$R_BR1_SWP1" clsact + ip netns exec "$R_NS_BRIDGE1" $TC filter add dev "$R_BR1_SWP1" ingress \ + protocol all flower skip_hw \ + action frer recover index $IDX_RELAY_RCVY \ + action mirred egress redirect dev "$R_BR1_DOWNLINK" + + # bridge1 / br1_downlink ingress: redirect VLAN 100 replies directly to b= r1_swp0 + ip netns exec "$R_NS_BRIDGE1" $TC qdisc add dev "$R_BR1_DOWNLINK" clsact + ip netns exec "$R_NS_BRIDGE1" $TC filter add dev "$R_BR1_DOWNLINK" ingres= s \ + protocol 802.1Q flower skip_hw vlan_id "$R_VLAN" \ + action mirred egress redirect dev "$R_BR1_SWP0" + + # Capture ICMP echo-requests on listener_eth.VLAN to verify exactly + # PING_COUNT deduplicated frames reach the listener after recovery. + local pcap cap_count + pcap=3D$(mktemp /tmp/frer_relay_XXXXXX.pcap) + capture_start_on "$R_NS_LISTENER" "${R_LISTENER_ETH}.${R_VLAN}" \ + "$pcap" "icmp[icmptype] =3D=3D icmp-echo" + + ip netns exec "$R_NS_TALKER" \ + $PING -c "$PING_COUNT" -W "$PING_TIMEOUT" -i 0.2 -q \ + "$R_IP_LISTENER" >/dev/null 2>&1 || ping_rc=3D$? + + capture_stop + cap_count=3D$(capture_count_on "$R_NS_LISTENER" "$pcap") + rm -f "$pcap" + + dump_br1_swp0=3D$(ip netns exec "$R_NS_BRIDGE1" \ + $TC -s filter show dev "$R_BR1_SWP0" ingress 2>/dev/null) + + teardown_relay_tc + for ns in "$R_NS_TALKER" "$R_NS_BRIDGE0" "$R_NS_BRIDGE1" "$R_NS_LISTENER"= ; do + ip netns del "$ns" 2>/dev/null || true + done + + total_passed=3D$(tc_stat "$dump_br1_swp0" "passed") + total_discarded=3D$(tc_stat "$dump_br1_swp0" "discarded") + local tagless + tagless=3D$(tc_stat "$dump_br1_swp0" "tagless") + total_discarded=3D$((total_discarded - tagless)) + + echo "# relay e2e: ping_rc=3D$ping_rc cap=3D$cap_count" \ + "passed=3D$total_passed discarded=3D$total_discarded" + + [ "$ping_rc" -eq 0 ] || result=3D"fail" + [ "$cap_count" -eq "$PING_COUNT" ] || result=3D"fail" + [ "$total_passed" -ge "$PING_COUNT" ] || result=3D"fail" + [ "$total_discarded" -ge "$PING_COUNT" ] || result=3D"fail" + + if [ "$result" =3D "pass" ]; then + ksft_test_result_pass \ + "relay e2e: ping OK, cap=3D$cap_count " \ + "passed=3D$total_passed discarded=3D$total_discarded" + else + ksft_test_result_fail \ + "relay e2e: ping_rc=3D$ping_rc cap=3D$cap_count " \ + "passed=3D$total_passed discarded=3D$total_discarded" \ + "(expected ping OK, cap=3D$PING_COUNT," \ + "passed>=3D$PING_COUNT, discarded>=3D$PING_COUNT)" + fi +} + +# ------------------------------------------------------------------------= ---- +# Main +# ------------------------------------------------------------------------= ---- +main() +{ + ksft_print_header + check_prerequisites + load_module + setup_topology + + if ! check_frer_action; then + ksft_set_plan "$NUM_TESTS" + for i in $(seq 1 "$NUM_TESTS"); do + ksft_test_result_skip \ + "frer action not available in this kernel (test $i)" + done + ksft_print_cnts + exit "$KSFT_SKIP" + fi + + ksft_set_plan "$NUM_TESTS" + + test_push_verify_bond # TEST 1: push on a0/b0, no recover, R-TAG o= n both paths + test_shared_recover_bond # TEST 2: shared recover, dedup, ping succee= ds + test_individual_recover_bond # TEST 3: individual recover, no dedup, doub= le frames + test_no_tag_pop_bond # TEST 4: shared recover without tag-pop, R-= TAG preserved + test_simple_point_to_point # TEST 5: single-path p2p, no bond + test_relay_e2e # TEST 6: relay bridge topology + + ksft_print_cnts + + [ "$_ksft_fail" -eq 0 ] && ksft_exit_pass || ksft_exit_fail +} + +main "$@" --=20 2.17.1