[RFC, net-next] net: qos: introduce a redundancy flow action
From: Xiaoliang Yang
Date: Tue Nov 17 2020 - 01:22:04 EST
This patch introduce a redundancy flow action to implement frame
replication and elimination for reliability, which is defined in
IEEE P802.1CB.
There are two modes for redundancy action: generator and recover mode.
Generator mode add redundancy tag and replicate the frame to different
egress ports. Recover mode drop the repeat frames and remove redundancy
tag from the frame.
Below is the setting example in user space:
> tc qdisc add dev swp0 clsact
> tc filter add dev swp0 ingress protocol 802.1Q flower \
skip_hw dst_mac 00:01:02:03:04:05 vlan_id 1 \
action redundancy generator split dev swp1 dev swp2
> tc filter add dev swp0 ingress protocol 802.1Q flower
skip_hw dst_mac 00:01:02:03:04:06 vlan_id 1 \
action redundancy recover
Signed-off-by: Xiaoliang Yang <xiaoliang.yang_1@xxxxxxx>
---
include/net/flow_offload.h | 6 +
include/net/tc_act/tc_redundancy.h | 69 +++
include/uapi/linux/if_ether.h | 1 +
include/uapi/linux/pkt_cls.h | 1 +
include/uapi/linux/tc_act/tc_redundancy.h | 36 ++
net/sched/Kconfig | 14 +
net/sched/Makefile | 1 +
net/sched/act_redundancy.c | 495 ++++++++++++++++++++++
net/sched/cls_api.c | 31 ++
9 files changed, 654 insertions(+)
create mode 100644 include/net/tc_act/tc_redundancy.h
create mode 100644 include/uapi/linux/tc_act/tc_redundancy.h
create mode 100644 net/sched/act_redundancy.c
diff --git a/include/net/flow_offload.h b/include/net/flow_offload.h
index 123b1e9ea304..aed41f3801b7 100644
--- a/include/net/flow_offload.h
+++ b/include/net/flow_offload.h
@@ -147,6 +147,7 @@ enum flow_action_id {
FLOW_ACTION_MPLS_POP,
FLOW_ACTION_MPLS_MANGLE,
FLOW_ACTION_GATE,
+ FLOW_ACTION_REDUNDANCY,
NUM_FLOW_ACTIONS,
};
@@ -271,6 +272,11 @@ struct flow_action_entry {
u32 num_entries;
struct action_gate_entry *entries;
} gate;
+ struct {
+ u8 mode;
+ u32 split_num;
+ struct net_device **split_devs;
+ } redundancy;
};
struct flow_action_cookie *cookie; /* user defined action cookie */
};
diff --git a/include/net/tc_act/tc_redundancy.h b/include/net/tc_act/tc_redundancy.h
new file mode 100644
index 000000000000..77b043636f93
--- /dev/null
+++ b/include/net/tc_act/tc_redundancy.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright 2020 NXP */
+
+#ifndef __NET_TC_REDUNDANCY_H
+#define __NET_TC_REDUNDANCY_H
+
+#include <net/act_api.h>
+#include <linux/tc_act/tc_redundancy.h>
+
+struct tcf_redundancy_split_dev {
+ struct list_head list;
+ struct net_device *dev;
+};
+
+struct tcf_redundancy {
+ struct tc_action common;
+ u8 mode;
+ struct list_head split_list;
+ u32 gen_seq_num;
+ u32 sequence_history;
+ u32 recov_seq_num;
+};
+
+#define to_redundancy(a) ((struct tcf_redundancy *)a)
+
+static inline bool is_tcf_redundancy(const struct tc_action *a)
+{
+#ifdef CONFIG_NET_CLS_ACT
+ if (a->ops && a->ops->id == TCA_ID_REDUNDANCY)
+ return true;
+#endif
+ return false;
+}
+
+static inline u8 tcf_redundancy_mode(const struct tc_action *a)
+{
+ u8 mode;
+
+ mode = to_redundancy(a)->mode;
+
+ return mode;
+}
+
+static inline struct net_device **
+ tcf_redundancy_create_dev_array(const struct tc_action *a, int *len)
+{
+ struct tcf_redundancy_split_dev *entry;
+ struct tcf_redundancy *red_act;
+ struct net_device **devices;
+ int i = 0;
+
+ red_act = to_redundancy(a);
+
+ list_for_each_entry(entry, &red_act->split_list, list)
+ i++;
+
+ devices = kcalloc(i, sizeof(*devices), GFP_ATOMIC);
+ if (!devices)
+ return NULL;
+ *len = i;
+
+ i = 0;
+ list_for_each_entry(entry, &red_act->split_list, list)
+ devices[i++] = entry->dev;
+
+ return devices;
+}
+
+#endif
diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h
index a0b637911d3c..c465d68b1d93 100644
--- a/include/uapi/linux/if_ether.h
+++ b/include/uapi/linux/if_ether.h
@@ -114,6 +114,7 @@
#define ETH_P_EDSA 0xDADA /* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
#define ETH_P_DSA_8021Q 0xDADB /* Fake VLAN Header for DSA [ NOT AN OFFICIALLY REGISTERED 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 REGISTERED ID ] */
#define ETH_P_802_3_MIN 0x0600 /* If the value in the ethernet type is less than this value
diff --git a/include/uapi/linux/pkt_cls.h b/include/uapi/linux/pkt_cls.h
index ee95f42fb0ec..17a82cae74f4 100644
--- a/include/uapi/linux/pkt_cls.h
+++ b/include/uapi/linux/pkt_cls.h
@@ -135,6 +135,7 @@ enum tca_id {
TCA_ID_MPLS,
TCA_ID_CT,
TCA_ID_GATE,
+ TCA_ID_REDUNDANCY,
/* other actions go here */
__TCA_ID_MAX = 255
};
diff --git a/include/uapi/linux/tc_act/tc_redundancy.h b/include/uapi/linux/tc_act/tc_redundancy.h
new file mode 100644
index 000000000000..2ddffff73f24
--- /dev/null
+++ b/include/uapi/linux/tc_act/tc_redundancy.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/* Copyright 2020 NXP */
+
+#ifndef __LINUX_TC_REDUNDANCY_H
+#define __LINUX_TC_REDUNDANCY_H
+
+#include <linux/pkt_cls.h>
+
+struct tc_redundancy {
+ tc_gen;
+};
+
+enum {
+ TCA_REDUNDANCY_ENTRY_UNSPEC,
+ TCA_REDUNDANCY_ENTRY_PORT,
+ __TCA_REDUNDANCY_ENTRY_MAX,
+};
+#define TCA_REDUNDANCY_ENTRY_MAX (__TCA_REDUNDANCY_ENTRY_MAX - 1)
+
+enum {
+ TCA_REDUNDANCY_UNSPEC,
+ TCA_REDUNDANCY_TM,
+ TCA_REDUNDANCY_PARMS,
+ TCA_REDUNDANCY_PAD,
+ TCA_REDUNDANCY_MODE,
+ TCA_REDUNDANCY_SPLITLIST,
+ __TCA_REDUNDANCY_MAX,
+};
+#define TCA_REDUNDANCY_MAX (__TCA_REDUNDANCY_MAX - 1)
+
+enum {
+ TCA_REDUNDANCY_GENERATOR,
+ TCA_REDUNDANCY_RECOVER,
+};
+
+#endif
diff --git a/net/sched/Kconfig b/net/sched/Kconfig
index a3b37d88800e..fba452fb9bd5 100644
--- a/net/sched/Kconfig
+++ b/net/sched/Kconfig
@@ -997,6 +997,20 @@ config NET_ACT_GATE
To compile this code as a module, choose M here: the
module will be called act_gate.
+config NET_ACT_REDUNDANCY
+ tristate "Frame redundancy tc action"
+ depends on NET_CLS_ACT
+ help
+ Say Y here to support frame replication and elimination for
+ reliability, which is defined by IEEE 802.1CB.
+ This action allow to control the ingress flow to split to
+ multiple egress ports, each frame will add a redundancy tag.
+ It also allow to remove redundancy tag and drop repeat frames.
+
+ If unsure, say N.
+ To compile this code as a module, choose M here: the
+ module will be called act_redundancy.
+
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 66bbf9a98f9e..1809e894510f 100644
--- a/net/sched/Makefile
+++ b/net/sched/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_NET_IFE_SKBTCINDEX) += act_meta_skbtcindex.o
obj-$(CONFIG_NET_ACT_TUNNEL_KEY)+= act_tunnel_key.o
obj-$(CONFIG_NET_ACT_CT) += act_ct.o
obj-$(CONFIG_NET_ACT_GATE) += act_gate.o
+obj-$(CONFIG_NET_ACT_REDUNDANCY) += act_redundancy.o
obj-$(CONFIG_NET_SCH_FIFO) += sch_fifo.o
obj-$(CONFIG_NET_SCH_CBQ) += sch_cbq.o
obj-$(CONFIG_NET_SCH_HTB) += sch_htb.o
diff --git a/net/sched/act_redundancy.c b/net/sched/act_redundancy.c
new file mode 100644
index 000000000000..c937b772c29d
--- /dev/null
+++ b/net/sched/act_redundancy.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright 2020 NXP */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <net/act_api.h>
+#include <net/netlink.h>
+#include <net/pkt_cls.h>
+#include <net/tc_act/tc_redundancy.h>
+
+#define SEQ_HISTORY_LEN 32
+
+static unsigned int redundancy_net_id;
+static struct tc_action_ops act_redundancy_ops;
+
+struct redundancy_tag {
+ __be16 proto;
+ __be16 reserved;
+ __be16 sequence;
+} __packed;
+
+struct rtag_ethhdr {
+ unsigned char h_dest[ETH_ALEN];
+ unsigned char h_source[ETH_ALEN];
+ struct redundancy_tag h_rtag;
+ __be16 h_proto;
+} __packed;
+
+struct rtag_vlan_ethhdr {
+ unsigned char h_dest[ETH_ALEN];
+ unsigned char h_source[ETH_ALEN];
+ __be16 h_vlan_proto;
+ __be16 h_vlan_TCI;
+ struct redundancy_tag h_rtag;
+ __be16 h_proto;
+} __packed;
+
+static const struct nla_policy entry_policy[TCA_REDUNDANCY_ENTRY_MAX + 1] = {
+ [TCA_REDUNDANCY_ENTRY_PORT] = { .type = NLA_U32 },
+};
+
+static const struct nla_policy red_policy[TCA_REDUNDANCY_MAX + 1] = {
+ [TCA_REDUNDANCY_PARMS] =
+ NLA_POLICY_EXACT_LEN(sizeof(struct tc_redundancy)),
+ [TCA_REDUNDANCY_MODE] = { .type = NLA_U8 },
+ [TCA_REDUNDANCY_SPLITLIST] = { .type = NLA_NESTED },
+};
+
+static void tcf_red_release_splitlist(struct list_head *listhead)
+{
+ struct tcf_redundancy_split_dev *entry, *e;
+
+ list_for_each_entry_safe(entry, e, listhead, list) {
+ list_del(&entry->list);
+ kfree(entry);
+ }
+}
+
+static int tcf_red_parse_splitlist(struct net *net, struct nlattr *list_attr,
+ struct tcf_redundancy *red_act,
+ struct netlink_ext_ack *extack)
+{
+ struct nlattr *tb[TCA_REDUNDANCY_ENTRY_MAX + 1] = { };
+ struct tcf_redundancy_split_dev *entry;
+ struct net_device *dev;
+ struct nlattr *n;
+ int err, rem;
+ u32 ifindex;
+
+ if (!list_attr)
+ return -EINVAL;
+
+ nla_for_each_nested(n, list_attr, rem) {
+ err = nla_parse_nested(tb, TCA_REDUNDANCY_ENTRY_MAX, n,
+ entry_policy, extack);
+ if (err < 0) {
+ NL_SET_ERR_MSG(extack, "Could not parse nested entry");
+ return -EINVAL;
+ }
+ ifindex = nla_get_u32(tb[TCA_REDUNDANCY_ENTRY_PORT]);
+ dev = dev_get_by_index(net, ifindex);
+ entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+ if (!entry) {
+ NL_SET_ERR_MSG(extack, "Not enough memory for entry");
+ tcf_red_release_splitlist(&red_act->split_list);
+ return -ENOMEM;
+ }
+ entry->dev = dev;
+ list_add_tail(&entry->list, &red_act->split_list);
+ }
+ return 0;
+}
+
+static int tcf_red_init(struct net *net, struct nlattr *nla,
+ struct nlattr *est, struct tc_action **a,
+ int ovr, int bind, bool rtnl_held,
+ struct tcf_proto *tp, u32 flags,
+ struct netlink_ext_ack *extack)
+{
+ struct tc_action_net *tn = net_generic(net, redundancy_net_id);
+ struct nlattr *tb[TCA_REDUNDANCY_MAX + 1];
+ struct tcf_chain *goto_ch = NULL;
+ struct tcf_redundancy *red_act;
+ struct tc_redundancy *parm;
+ int ret = 0, err, index;
+
+ if (!nla)
+ return -EINVAL;
+
+ err = nla_parse_nested(tb, TCA_REDUNDANCY_MAX, nla, red_policy, extack);
+ if (err < 0)
+ return err;
+
+ if (!tb[TCA_REDUNDANCY_PARMS])
+ return -EINVAL;
+
+ parm = nla_data(tb[TCA_REDUNDANCY_PARMS]);
+ index = parm->index;
+
+ err = tcf_idr_check_alloc(tn, &index, a, bind);
+ if (err < 0)
+ return err;
+
+ if (err && bind)
+ return 0;
+
+ if (!err) {
+ ret = tcf_idr_create(tn, index, est, a,
+ &act_redundancy_ops, bind, false, 0);
+
+ if (ret) {
+ tcf_idr_cleanup(tn, index);
+ return ret;
+ }
+ ret = ACT_P_CREATED;
+ } else if (!ovr) {
+ tcf_idr_release(*a, bind);
+ return -EEXIST;
+ }
+
+ err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
+ if (err < 0)
+ goto release_idr;
+
+ red_act = to_redundancy(*a);
+
+ spin_lock_bh(&red_act->tcf_lock);
+ goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
+
+ if (ret == ACT_P_CREATED)
+ INIT_LIST_HEAD(&red_act->split_list);
+
+ red_act->mode = nla_get_u8(tb[TCA_REDUNDANCY_MODE]);
+ if (tb[TCA_REDUNDANCY_SPLITLIST])
+ tcf_red_parse_splitlist(net, tb[TCA_REDUNDANCY_SPLITLIST],
+ red_act, extack);
+
+ red_act->gen_seq_num = 0;
+ red_act->recov_seq_num = 0;
+ red_act->sequence_history = 0xFFFFFFFF;
+ spin_unlock_bh(&red_act->tcf_lock);
+
+ if (goto_ch)
+ tcf_chain_put_by_act(goto_ch);
+
+ return ret;
+
+release_idr:
+ tcf_idr_release(*a, bind);
+ return err;
+}
+
+static int tcf_red_tag_insert(struct sk_buff *skb,
+ struct tcf_redundancy *red_act)
+{
+ int rtag_len = sizeof(struct redundancy_tag);
+ struct rtag_vlan_ethhdr *red_vlan_hdr;
+ struct rtag_ethhdr *red_hdr;
+ struct redundancy_tag *rtag;
+ unsigned char *dst, *src;
+
+ red_vlan_hdr = (struct rtag_vlan_ethhdr *)skb_mac_header(skb);
+ if (red_vlan_hdr->h_vlan_proto == htons(ETH_P_8021Q)) {
+ rtag = &red_vlan_hdr->h_rtag;
+ } else {
+ red_hdr = (struct rtag_ethhdr *)skb_mac_header(skb);
+ rtag = &red_hdr->h_rtag;
+ }
+
+ if (rtag->proto == htons(ETH_P_RTAG))
+ return 0;
+
+ if (skb_cow_head(skb, rtag_len) < 0)
+ return -ENOMEM;
+
+ src = skb_mac_header(skb);
+ skb->data = skb_mac_header(skb);
+ skb_push(skb, rtag_len);
+
+ skb_reset_mac_header(skb);
+ dst = skb_mac_header(skb);
+ memmove(dst, src, (unsigned char *)rtag - src);
+
+ rtag--;
+ rtag->proto = htons(ETH_P_RTAG);
+ rtag->sequence = htons(red_act->gen_seq_num);
+ rtag->reserved = 0;
+
+ return 0;
+}
+
+static void tcf_red_recover_reset(u16 sequence, struct tcf_redundancy *red_act)
+{
+ red_act->sequence_history = 0xFFFFFFFF;
+ red_act->recov_seq_num = sequence;
+}
+
+static int tcf_red_recover_drop(u16 sequence,
+ struct tcf_redundancy *red_act)
+{
+ int drop = 1;
+ u32 len;
+ int i, bit;
+
+ if (sequence > (red_act->recov_seq_num + SEQ_HISTORY_LEN) ||
+ sequence < (red_act->recov_seq_num - SEQ_HISTORY_LEN)) {
+ tcf_red_recover_reset(sequence, red_act);
+ return 0;
+ }
+
+ if (sequence > red_act->recov_seq_num) {
+ len = sequence - red_act->recov_seq_num;
+ for (i = 0; i < len; i++) {
+ bit = SEQ_HISTORY_LEN - 1 - i;
+ if (!(red_act->sequence_history & BIT(bit)))
+ red_act->tcf_qstats.drops++;
+ }
+ red_act->sequence_history <<= len;
+ red_act->sequence_history |= 1;
+ red_act->recov_seq_num = sequence;
+
+ return 0;
+ }
+
+ len = red_act->recov_seq_num - sequence;
+
+ if (red_act->sequence_history & (1 << len))
+ return drop;
+
+ red_act->sequence_history |= (1 << len);
+
+ return 0;
+}
+
+static int tcf_red_tag_remove(struct sk_buff *skb,
+ struct tcf_redundancy *red_act)
+{
+ int rtag_len = sizeof(struct redundancy_tag);
+ struct rtag_vlan_ethhdr *red_vlan_hdr;
+ struct rtag_ethhdr *red_hdr;
+ struct redundancy_tag *rtag;
+ unsigned char *dst, *src;
+ u16 sequence;
+
+ red_vlan_hdr = (struct rtag_vlan_ethhdr *)skb_mac_header(skb);
+ if (red_vlan_hdr->h_vlan_proto == htons(ETH_P_8021Q)) {
+ rtag = &red_vlan_hdr->h_rtag;
+ } else {
+ red_hdr = (struct rtag_ethhdr *)skb_mac_header(skb);
+ rtag = &red_hdr->h_rtag;
+ }
+
+ if (rtag->proto != htons(ETH_P_RTAG))
+ return 0;
+
+ sequence = ntohs(rtag->sequence);
+
+ src = skb_mac_header(skb);
+ skb->data = skb_mac_header(skb);
+ skb_pull(skb, rtag_len);
+
+ skb_reset_mac_header(skb);
+ dst = skb_mac_header(skb);
+ memmove(dst, src, (unsigned char *)rtag - src);
+
+ return tcf_red_recover_drop(sequence, red_act);
+}
+
+static int tcf_red_act(struct sk_buff *skb, const struct tc_action *a,
+ struct tcf_result *res)
+{
+ struct tcf_redundancy *red_act = to_redundancy(a);
+ struct tcf_redundancy_split_dev *entry;
+ struct net_device *splitdev;
+ struct sk_buff *skb2 = skb;
+ int err, ret, retval;
+
+ tcf_lastuse_update(&red_act->tcf_tm);
+ tcf_action_update_bstats(&red_act->common, skb);
+
+ retval = READ_ONCE(red_act->tcf_action);
+
+ if (red_act->mode == TCA_REDUNDANCY_RECOVER) {
+ /* Recover skb */
+ spin_lock(&red_act->tcf_lock);
+ ret = tcf_red_tag_remove(skb, red_act);
+ if (ret)
+ retval = TC_ACT_SHOT;
+
+ spin_unlock(&red_act->tcf_lock);
+ return retval;
+ }
+
+ err = tcf_red_tag_insert(skb, red_act);
+ if (err) {
+ tcf_action_inc_overlimit_qstats(&red_act->common);
+ return TC_ACT_SHOT;
+ }
+
+ list_for_each_entry(entry, &red_act->split_list, list) {
+ splitdev = entry->dev;
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ skb2->skb_iif = splitdev->ifindex;
+ skb2->dev = splitdev;
+ dev_queue_xmit(skb2);
+ }
+
+ spin_lock(&red_act->tcf_lock);
+ red_act->gen_seq_num++;
+ spin_unlock(&red_act->tcf_lock);
+
+ return retval;
+}
+
+static int dumping_entry(struct sk_buff *skb,
+ struct tcf_redundancy_split_dev *entry)
+{
+ struct nlattr *item;
+
+ item = nla_nest_start_noflag(skb, 0);
+ if (!item)
+ return -ENOSPC;
+
+ if (nla_put_u32(skb, TCA_REDUNDANCY_ENTRY_PORT,
+ entry->dev->ifindex))
+ goto nla_put_failure;
+
+ return nla_nest_end(skb, item);
+
+nla_put_failure:
+ nla_nest_cancel(skb, item);
+ return -1;
+}
+
+static int tcf_red_dump(struct sk_buff *skb, struct tc_action *a,
+ int bind, int ref)
+{
+ unsigned char *b = skb_tail_pointer(skb);
+ struct tcf_redundancy_split_dev *entry;
+ struct tcf_redundancy *red_act = to_redundancy(a);
+ struct tc_redundancy opt = {
+ .index = red_act->tcf_index,
+ .refcnt = refcount_read(&red_act->tcf_refcnt) - ref,
+ .bindcnt = atomic_read(&red_act->tcf_bindcnt) - bind,
+ };
+ struct tcf_t t;
+ struct nlattr *entry_list;
+
+ spin_lock_bh(&red_act->tcf_lock);
+ opt.action = red_act->tcf_action;
+
+ if (nla_put(skb, TCA_REDUNDANCY_PARMS, sizeof(opt), &opt))
+ goto nla_put_failure;
+
+ if (nla_put_u8(skb, TCA_REDUNDANCY_MODE, red_act->mode))
+ goto nla_put_failure;
+
+ entry_list = nla_nest_start_noflag(skb, TCA_REDUNDANCY_SPLITLIST);
+ if (!entry_list)
+ goto nla_put_failure;
+
+ list_for_each_entry(entry, &red_act->split_list, list) {
+ if (dumping_entry(skb, entry) < 0)
+ goto nla_put_failure;
+ }
+ nla_nest_end(skb, entry_list);
+
+ tcf_tm_dump(&t, &red_act->tcf_tm);
+ if (nla_put_64bit(skb, TCA_REDUNDANCY_TM, sizeof(t),
+ &t, TCA_REDUNDANCY_PAD))
+ goto nla_put_failure;
+ spin_unlock_bh(&red_act->tcf_lock);
+
+ return skb->len;
+
+nla_put_failure:
+ spin_unlock_bh(&red_act->tcf_lock);
+ nlmsg_trim(skb, b);
+
+ return -1;
+}
+
+static int tcf_red_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 = net_generic(net, redundancy_net_id);
+
+ return tcf_generic_walker(tn, skb, cb, type, ops, extack);
+}
+
+static int tcf_red_search(struct net *net, struct tc_action **a, u32 index)
+{
+ struct tc_action_net *tn = net_generic(net, redundancy_net_id);
+
+ return tcf_idr_search(tn, a, index);
+}
+
+static void tcf_red_stats_update(struct tc_action *a, u64 bytes, u64 packets,
+ u64 drops, u64 lastuse, bool hw)
+{
+ struct tcf_redundancy *red_act = to_redundancy(a);
+ struct tcf_t *tm = &red_act->tcf_tm;
+
+ tcf_action_update_stats(a, bytes, packets, drops, hw);
+ tm->lastuse = max_t(u64, tm->lastuse, lastuse);
+}
+
+static void tcf_red_cleanup(struct tc_action *a)
+{
+ struct tcf_redundancy *red_act = to_redundancy(a);
+
+ tcf_red_release_splitlist(&red_act->split_list);
+}
+
+static size_t tcf_red_get_fill_size(const struct tc_action *act)
+{
+ return nla_total_size(sizeof(struct tc_redundancy));
+}
+
+static struct tc_action_ops act_redundancy_ops = {
+ .kind = "redundancy",
+ .id = TCA_ID_REDUNDANCY,
+ .owner = THIS_MODULE,
+ .act = tcf_red_act,
+ .init = tcf_red_init,
+ .cleanup = tcf_red_cleanup,
+ .dump = tcf_red_dump,
+ .walk = tcf_red_walker,
+ .stats_update = tcf_red_stats_update,
+ .get_fill_size = tcf_red_get_fill_size,
+ .lookup = tcf_red_search,
+ .size = sizeof(struct tcf_redundancy),
+};
+
+static __net_init int redundancy_init_net(struct net *net)
+{
+ struct tc_action_net *tn = net_generic(net, redundancy_net_id);
+
+ return tc_action_net_init(net, tn, &act_redundancy_ops);
+}
+
+static void __net_exit redundancy_exit_net(struct list_head *net_list)
+{
+ tc_action_net_exit(net_list, redundancy_net_id);
+};
+
+static struct pernet_operations redundancy_net_ops = {
+ .init = redundancy_init_net,
+ .exit_batch = redundancy_exit_net,
+ .id = &redundancy_net_id,
+ .size = sizeof(struct tc_action_net),
+};
+
+static int __init redundancy_init_module(void)
+{
+ return tcf_register_action(&act_redundancy_ops, &redundancy_net_ops);
+}
+
+static void __exit redundancy_cleanup_module(void)
+{
+ tcf_unregister_action(&act_redundancy_ops, &redundancy_net_ops);
+}
+
+module_init(redundancy_init_module);
+module_exit(redundancy_cleanup_module);
+MODULE_LICENSE("GPL v2");
diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c
index ba0715ee9eac..68ae5ffe07a0 100644
--- a/net/sched/cls_api.c
+++ b/net/sched/cls_api.c
@@ -39,6 +39,7 @@
#include <net/tc_act/tc_ct.h>
#include <net/tc_act/tc_mpls.h>
#include <net/tc_act/tc_gate.h>
+#include <net/tc_act/tc_redundancy.h>
#include <net/flow_offload.h>
extern const struct nla_policy rtm_tca_policy[TCA_MAX + 1];
@@ -3534,6 +3535,30 @@ static int tcf_gate_get_entries(struct flow_action_entry *entry,
return 0;
}
+static void tcf_redundancy_devlist_destructor(void *priv)
+{
+ struct netdevice **devices = priv;
+
+ kfree(devices);
+}
+
+static int tcf_redundancy_get_splitdevs(struct flow_action_entry *entry,
+ const struct tc_action *act)
+{
+ u32 len;
+
+ entry->redundancy.split_devs = tcf_redundancy_create_dev_array(act, &len);
+ if (!entry->redundancy.split_devs)
+ return -EINVAL;
+
+ entry->redundancy.split_num = len;
+
+ entry->destructor = tcf_redundancy_devlist_destructor;
+ entry->destructor_priv = entry->redundancy.split_devs;
+
+ return 0;
+}
+
static enum flow_action_hw_stats tc_act_hw_stats(u8 hw_stats)
{
if (WARN_ON_ONCE(hw_stats > TCA_ACT_HW_STATS_ANY))
@@ -3703,6 +3728,12 @@ int tc_setup_flow_action(struct flow_action *flow_action,
err = tcf_gate_get_entries(entry, act);
if (err)
goto err_out_locked;
+ } else if (is_tcf_redundancy(act)) {
+ entry->id = FLOW_ACTION_REDUNDANCY;
+ entry->redundancy.mode = tcf_redundancy_mode(act);
+ err = tcf_redundancy_get_splitdevs(entry, act);
+ if (err)
+ goto err_out;
} else {
err = -EOPNOTSUPP;
goto err_out_locked;
--
2.17.1