xref: /openbmc/linux/net/sched/act_skbedit.c (revision 4a6a676f8c16ec17d2f8d69ce3b5d680277ed0d2)
19952f691SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2ca9b0e27SAlexander Duyck /*
3ca9b0e27SAlexander Duyck  * Copyright (c) 2008, Intel Corporation.
4ca9b0e27SAlexander Duyck  *
5ca9b0e27SAlexander Duyck  * Author: Alexander Duyck <alexander.h.duyck@intel.com>
6ca9b0e27SAlexander Duyck  */
7ca9b0e27SAlexander Duyck 
8ca9b0e27SAlexander Duyck #include <linux/module.h>
9ca9b0e27SAlexander Duyck #include <linux/init.h>
10ca9b0e27SAlexander Duyck #include <linux/kernel.h>
11ca9b0e27SAlexander Duyck #include <linux/skbuff.h>
12ca9b0e27SAlexander Duyck #include <linux/rtnetlink.h>
13ca9b0e27SAlexander Duyck #include <net/netlink.h>
14ca9b0e27SAlexander Duyck #include <net/pkt_sched.h>
15e7e3728bSQiaobin Fu #include <net/ip.h>
16e7e3728bSQiaobin Fu #include <net/ipv6.h>
17e7e3728bSQiaobin Fu #include <net/dsfield.h>
18ec7727bbSDavide Caratti #include <net/pkt_cls.h>
19ca9b0e27SAlexander Duyck 
20ca9b0e27SAlexander Duyck #include <linux/tc_act/tc_skbedit.h>
21ca9b0e27SAlexander Duyck #include <net/tc_act/tc_skbedit.h>
22ca9b0e27SAlexander Duyck 
23a85a970aSWANG Cong static struct tc_action_ops act_skbedit_ops;
24ddf97ccdSWANG Cong 
2538a6f086STonghao Zhang static u16 tcf_skbedit_hash(struct tcf_skbedit_params *params,
2638a6f086STonghao Zhang 			    struct sk_buff *skb)
2738a6f086STonghao Zhang {
2838a6f086STonghao Zhang 	u16 queue_mapping = params->queue_mapping;
2938a6f086STonghao Zhang 
3038a6f086STonghao Zhang 	if (params->flags & SKBEDIT_F_TXQ_SKBHASH) {
3138a6f086STonghao Zhang 		u32 hash = skb_get_hash(skb);
3238a6f086STonghao Zhang 
3338a6f086STonghao Zhang 		queue_mapping += hash % params->mapping_mod;
3438a6f086STonghao Zhang 	}
3538a6f086STonghao Zhang 
3638a6f086STonghao Zhang 	return netdev_cap_txqueue(skb->dev, queue_mapping);
3738a6f086STonghao Zhang }
3838a6f086STonghao Zhang 
3945da1dacSJamal Hadi Salim static int tcf_skbedit_act(struct sk_buff *skb, const struct tc_action *a,
40ca9b0e27SAlexander Duyck 			   struct tcf_result *res)
41ca9b0e27SAlexander Duyck {
42a85a970aSWANG Cong 	struct tcf_skbedit *d = to_skbedit(a);
43c749cddaSDavide Caratti 	struct tcf_skbedit_params *params;
44c749cddaSDavide Caratti 	int action;
45ca9b0e27SAlexander Duyck 
469c4a4e48SJamal Hadi Salim 	tcf_lastuse_update(&d->tcf_tm);
4750dc9a85SAhmed S. Darwish 	bstats_update(this_cpu_ptr(d->common.cpu_bstats), skb);
48ca9b0e27SAlexander Duyck 
497fd4b288SPaolo Abeni 	params = rcu_dereference_bh(d->params);
50c749cddaSDavide Caratti 	action = READ_ONCE(d->tcf_action);
51c749cddaSDavide Caratti 
52c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_PRIORITY)
53c749cddaSDavide Caratti 		skb->priority = params->priority;
54c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_INHERITDSFIELD) {
55e7e3728bSQiaobin Fu 		int wlen = skb_network_offset(skb);
56e7e3728bSQiaobin Fu 
57d7bf2ebeSToke Høiland-Jørgensen 		switch (skb_protocol(skb, true)) {
58e7e3728bSQiaobin Fu 		case htons(ETH_P_IP):
59e7e3728bSQiaobin Fu 			wlen += sizeof(struct iphdr);
60e7e3728bSQiaobin Fu 			if (!pskb_may_pull(skb, wlen))
61e7e3728bSQiaobin Fu 				goto err;
62e7e3728bSQiaobin Fu 			skb->priority = ipv4_get_dsfield(ip_hdr(skb)) >> 2;
63e7e3728bSQiaobin Fu 			break;
64e7e3728bSQiaobin Fu 
65e7e3728bSQiaobin Fu 		case htons(ETH_P_IPV6):
66e7e3728bSQiaobin Fu 			wlen += sizeof(struct ipv6hdr);
67e7e3728bSQiaobin Fu 			if (!pskb_may_pull(skb, wlen))
68e7e3728bSQiaobin Fu 				goto err;
69e7e3728bSQiaobin Fu 			skb->priority = ipv6_get_dsfield(ipv6_hdr(skb)) >> 2;
70e7e3728bSQiaobin Fu 			break;
71e7e3728bSQiaobin Fu 		}
72e7e3728bSQiaobin Fu 	}
73c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_QUEUE_MAPPING &&
742f1e85b1STonghao Zhang 	    skb->dev->real_num_tx_queues > params->queue_mapping) {
752f1e85b1STonghao Zhang #ifdef CONFIG_NET_EGRESS
762f1e85b1STonghao Zhang 		netdev_xmit_skip_txqueue(true);
772f1e85b1STonghao Zhang #endif
7838a6f086STonghao Zhang 		skb_set_queue_mapping(skb, tcf_skbedit_hash(params, skb));
792f1e85b1STonghao Zhang 	}
80c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_MARK) {
81c749cddaSDavide Caratti 		skb->mark &= ~params->mask;
82c749cddaSDavide Caratti 		skb->mark |= params->mark & params->mask;
834fe77d82SAntonio Quartulli 	}
84c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_PTYPE)
85c749cddaSDavide Caratti 		skb->pkt_type = params->ptype;
86c749cddaSDavide Caratti 	return action;
877fd4b288SPaolo Abeni 
88e7e3728bSQiaobin Fu err:
896f3dfb0dSDavide Caratti 	qstats_drop_inc(this_cpu_ptr(d->common.cpu_qstats));
907fd4b288SPaolo Abeni 	return TC_ACT_SHOT;
91ca9b0e27SAlexander Duyck }
92ca9b0e27SAlexander Duyck 
93837cb17dSPetr Machata static void tcf_skbedit_stats_update(struct tc_action *a, u64 bytes,
944b61d3e8SPo Liu 				     u64 packets, u64 drops,
954b61d3e8SPo Liu 				     u64 lastuse, bool hw)
96837cb17dSPetr Machata {
97837cb17dSPetr Machata 	struct tcf_skbedit *d = to_skbedit(a);
98837cb17dSPetr Machata 	struct tcf_t *tm = &d->tcf_tm;
99837cb17dSPetr Machata 
1004b61d3e8SPo Liu 	tcf_action_update_stats(a, bytes, packets, drops, hw);
101837cb17dSPetr Machata 	tm->lastuse = max_t(u64, tm->lastuse, lastuse);
102837cb17dSPetr Machata }
103837cb17dSPetr Machata 
104ca9b0e27SAlexander Duyck static const struct nla_policy skbedit_policy[TCA_SKBEDIT_MAX + 1] = {
105ca9b0e27SAlexander Duyck 	[TCA_SKBEDIT_PARMS]		= { .len = sizeof(struct tc_skbedit) },
106ca9b0e27SAlexander Duyck 	[TCA_SKBEDIT_PRIORITY]		= { .len = sizeof(u32) },
107ca9b0e27SAlexander Duyck 	[TCA_SKBEDIT_QUEUE_MAPPING]	= { .len = sizeof(u16) },
1081c55d62eSjamal 	[TCA_SKBEDIT_MARK]		= { .len = sizeof(u32) },
109ff202ee1SJamal Hadi Salim 	[TCA_SKBEDIT_PTYPE]		= { .len = sizeof(u16) },
1104fe77d82SAntonio Quartulli 	[TCA_SKBEDIT_MASK]		= { .len = sizeof(u32) },
111e7e3728bSQiaobin Fu 	[TCA_SKBEDIT_FLAGS]		= { .len = sizeof(u64) },
11238a6f086STonghao Zhang 	[TCA_SKBEDIT_QUEUE_MAPPING_MAX]	= { .len = sizeof(u16) },
113ca9b0e27SAlexander Duyck };
114ca9b0e27SAlexander Duyck 
115c1b52739SBenjamin LaHaise static int tcf_skbedit_init(struct net *net, struct nlattr *nla,
116a85a970aSWANG Cong 			    struct nlattr *est, struct tc_action **a,
117abbb0d33SVlad Buslov 			    struct tcf_proto *tp, u32 act_flags,
118789871bbSVlad Buslov 			    struct netlink_ext_ack *extack)
119ca9b0e27SAlexander Duyck {
120acd0a7abSZhengchao Shao 	struct tc_action_net *tn = net_generic(net, act_skbedit_ops.net_id);
121695176bfSCong Wang 	bool bind = act_flags & TCA_ACT_FLAGS_BIND;
1226d7a8df6SVlad Buslov 	struct tcf_skbedit_params *params_new;
123ca9b0e27SAlexander Duyck 	struct nlattr *tb[TCA_SKBEDIT_MAX + 1];
124ec7727bbSDavide Caratti 	struct tcf_chain *goto_ch = NULL;
125ca9b0e27SAlexander Duyck 	struct tc_skbedit *parm;
126ca9b0e27SAlexander Duyck 	struct tcf_skbedit *d;
1274fe77d82SAntonio Quartulli 	u32 flags = 0, *priority = NULL, *mark = NULL, *mask = NULL;
128ff202ee1SJamal Hadi Salim 	u16 *queue_mapping = NULL, *ptype = NULL;
12938a6f086STonghao Zhang 	u16 mapping_mod = 1;
130b2313077SWANG Cong 	bool exists = false;
131b2313077SWANG Cong 	int ret = 0, err;
1327be8ef2cSDmytro Linkin 	u32 index;
133ca9b0e27SAlexander Duyck 
134ca9b0e27SAlexander Duyck 	if (nla == NULL)
135ca9b0e27SAlexander Duyck 		return -EINVAL;
136ca9b0e27SAlexander Duyck 
1378cb08174SJohannes Berg 	err = nla_parse_nested_deprecated(tb, TCA_SKBEDIT_MAX, nla,
1388cb08174SJohannes Berg 					  skbedit_policy, NULL);
139ca9b0e27SAlexander Duyck 	if (err < 0)
140ca9b0e27SAlexander Duyck 		return err;
141ca9b0e27SAlexander Duyck 
142ca9b0e27SAlexander Duyck 	if (tb[TCA_SKBEDIT_PARMS] == NULL)
143ca9b0e27SAlexander Duyck 		return -EINVAL;
144ca9b0e27SAlexander Duyck 
145ca9b0e27SAlexander Duyck 	if (tb[TCA_SKBEDIT_PRIORITY] != NULL) {
146ca9b0e27SAlexander Duyck 		flags |= SKBEDIT_F_PRIORITY;
147ca9b0e27SAlexander Duyck 		priority = nla_data(tb[TCA_SKBEDIT_PRIORITY]);
148ca9b0e27SAlexander Duyck 	}
149ca9b0e27SAlexander Duyck 
150ca9b0e27SAlexander Duyck 	if (tb[TCA_SKBEDIT_QUEUE_MAPPING] != NULL) {
151*4a6a676fSAmritha Nambiar 		if (is_tcf_skbedit_ingress(act_flags) &&
152*4a6a676fSAmritha Nambiar 		    !(act_flags & TCA_ACT_FLAGS_SKIP_SW)) {
153*4a6a676fSAmritha Nambiar 			NL_SET_ERR_MSG_MOD(extack, "\"queue_mapping\" option on receive side is hardware only, use skip_sw");
154*4a6a676fSAmritha Nambiar 			return -EOPNOTSUPP;
155*4a6a676fSAmritha Nambiar 		}
156ca9b0e27SAlexander Duyck 		flags |= SKBEDIT_F_QUEUE_MAPPING;
157ca9b0e27SAlexander Duyck 		queue_mapping = nla_data(tb[TCA_SKBEDIT_QUEUE_MAPPING]);
158ca9b0e27SAlexander Duyck 	}
1591c55d62eSjamal 
160ff202ee1SJamal Hadi Salim 	if (tb[TCA_SKBEDIT_PTYPE] != NULL) {
161ff202ee1SJamal Hadi Salim 		ptype = nla_data(tb[TCA_SKBEDIT_PTYPE]);
162ff202ee1SJamal Hadi Salim 		if (!skb_pkt_type_ok(*ptype))
163ff202ee1SJamal Hadi Salim 			return -EINVAL;
164ff202ee1SJamal Hadi Salim 		flags |= SKBEDIT_F_PTYPE;
165ff202ee1SJamal Hadi Salim 	}
166ff202ee1SJamal Hadi Salim 
1671c55d62eSjamal 	if (tb[TCA_SKBEDIT_MARK] != NULL) {
1681c55d62eSjamal 		flags |= SKBEDIT_F_MARK;
1691c55d62eSjamal 		mark = nla_data(tb[TCA_SKBEDIT_MARK]);
1701c55d62eSjamal 	}
1711c55d62eSjamal 
1724fe77d82SAntonio Quartulli 	if (tb[TCA_SKBEDIT_MASK] != NULL) {
1734fe77d82SAntonio Quartulli 		flags |= SKBEDIT_F_MASK;
1744fe77d82SAntonio Quartulli 		mask = nla_data(tb[TCA_SKBEDIT_MASK]);
1754fe77d82SAntonio Quartulli 	}
1764fe77d82SAntonio Quartulli 
177e7e3728bSQiaobin Fu 	if (tb[TCA_SKBEDIT_FLAGS] != NULL) {
178e7e3728bSQiaobin Fu 		u64 *pure_flags = nla_data(tb[TCA_SKBEDIT_FLAGS]);
179e7e3728bSQiaobin Fu 
18038a6f086STonghao Zhang 		if (*pure_flags & SKBEDIT_F_TXQ_SKBHASH) {
18138a6f086STonghao Zhang 			u16 *queue_mapping_max;
18238a6f086STonghao Zhang 
18338a6f086STonghao Zhang 			if (!tb[TCA_SKBEDIT_QUEUE_MAPPING] ||
18438a6f086STonghao Zhang 			    !tb[TCA_SKBEDIT_QUEUE_MAPPING_MAX]) {
18538a6f086STonghao Zhang 				NL_SET_ERR_MSG_MOD(extack, "Missing required range of queue_mapping.");
18638a6f086STonghao Zhang 				return -EINVAL;
18738a6f086STonghao Zhang 			}
18838a6f086STonghao Zhang 
18938a6f086STonghao Zhang 			queue_mapping_max =
19038a6f086STonghao Zhang 				nla_data(tb[TCA_SKBEDIT_QUEUE_MAPPING_MAX]);
19138a6f086STonghao Zhang 			if (*queue_mapping_max < *queue_mapping) {
19238a6f086STonghao Zhang 				NL_SET_ERR_MSG_MOD(extack, "The range of queue_mapping is invalid, max < min.");
19338a6f086STonghao Zhang 				return -EINVAL;
19438a6f086STonghao Zhang 			}
19538a6f086STonghao Zhang 
19638a6f086STonghao Zhang 			mapping_mod = *queue_mapping_max - *queue_mapping + 1;
19738a6f086STonghao Zhang 			flags |= SKBEDIT_F_TXQ_SKBHASH;
19838a6f086STonghao Zhang 		}
199e7e3728bSQiaobin Fu 		if (*pure_flags & SKBEDIT_F_INHERITDSFIELD)
200e7e3728bSQiaobin Fu 			flags |= SKBEDIT_F_INHERITDSFIELD;
201e7e3728bSQiaobin Fu 	}
202e7e3728bSQiaobin Fu 
203ca9b0e27SAlexander Duyck 	parm = nla_data(tb[TCA_SKBEDIT_PARMS]);
2047be8ef2cSDmytro Linkin 	index = parm->index;
2057be8ef2cSDmytro Linkin 	err = tcf_idr_check_alloc(tn, &index, a, bind);
2060190c1d4SVlad Buslov 	if (err < 0)
2070190c1d4SVlad Buslov 		return err;
2080190c1d4SVlad Buslov 	exists = err;
2095e1567aeSJamal Hadi Salim 	if (exists && bind)
2105e1567aeSJamal Hadi Salim 		return 0;
2115e1567aeSJamal Hadi Salim 
2125e1567aeSJamal Hadi Salim 	if (!flags) {
213af5d0184SRoman Mashak 		if (exists)
21465a206c0SChris Mi 			tcf_idr_release(*a, bind);
2150190c1d4SVlad Buslov 		else
2167be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
2175e1567aeSJamal Hadi Salim 		return -EINVAL;
2185e1567aeSJamal Hadi Salim 	}
2195e1567aeSJamal Hadi Salim 
2205e1567aeSJamal Hadi Salim 	if (!exists) {
2217be8ef2cSDmytro Linkin 		ret = tcf_idr_create(tn, index, est, a,
22240bd094dSBaowen Zheng 				     &act_skbedit_ops, bind, true, act_flags);
2230190c1d4SVlad Buslov 		if (ret) {
2247be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
22586062033SWANG Cong 			return ret;
2260190c1d4SVlad Buslov 		}
227ca9b0e27SAlexander Duyck 
228a85a970aSWANG Cong 		d = to_skbedit(*a);
229ca9b0e27SAlexander Duyck 		ret = ACT_P_CREATED;
230ca9b0e27SAlexander Duyck 	} else {
231a85a970aSWANG Cong 		d = to_skbedit(*a);
232695176bfSCong Wang 		if (!(act_flags & TCA_ACT_FLAGS_REPLACE)) {
23365a206c0SChris Mi 			tcf_idr_release(*a, bind);
234ca9b0e27SAlexander Duyck 			return -EEXIST;
235ca9b0e27SAlexander Duyck 		}
2364e8ddd7fSVlad Buslov 	}
237ec7727bbSDavide Caratti 	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
238ec7727bbSDavide Caratti 	if (err < 0)
239ec7727bbSDavide Caratti 		goto release_idr;
240ca9b0e27SAlexander Duyck 
241c749cddaSDavide Caratti 	params_new = kzalloc(sizeof(*params_new), GFP_KERNEL);
242c749cddaSDavide Caratti 	if (unlikely(!params_new)) {
243ec7727bbSDavide Caratti 		err = -ENOMEM;
244ec7727bbSDavide Caratti 		goto put_chain;
245c749cddaSDavide Caratti 	}
246c749cddaSDavide Caratti 
247c749cddaSDavide Caratti 	params_new->flags = flags;
248ca9b0e27SAlexander Duyck 	if (flags & SKBEDIT_F_PRIORITY)
249c749cddaSDavide Caratti 		params_new->priority = *priority;
25038a6f086STonghao Zhang 	if (flags & SKBEDIT_F_QUEUE_MAPPING) {
251c749cddaSDavide Caratti 		params_new->queue_mapping = *queue_mapping;
25238a6f086STonghao Zhang 		params_new->mapping_mod = mapping_mod;
25338a6f086STonghao Zhang 	}
2541c55d62eSjamal 	if (flags & SKBEDIT_F_MARK)
255c749cddaSDavide Caratti 		params_new->mark = *mark;
256ff202ee1SJamal Hadi Salim 	if (flags & SKBEDIT_F_PTYPE)
257c749cddaSDavide Caratti 		params_new->ptype = *ptype;
2584fe77d82SAntonio Quartulli 	/* default behaviour is to use all the bits */
259c749cddaSDavide Caratti 	params_new->mask = 0xffffffff;
2604fe77d82SAntonio Quartulli 	if (flags & SKBEDIT_F_MASK)
261c749cddaSDavide Caratti 		params_new->mask = *mask;
2621c55d62eSjamal 
2636d7a8df6SVlad Buslov 	spin_lock_bh(&d->tcf_lock);
264ec7727bbSDavide Caratti 	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
265445d3749SPaul E. McKenney 	params_new = rcu_replace_pointer(d->params, params_new,
2666d7a8df6SVlad Buslov 					 lockdep_is_held(&d->tcf_lock));
2676d7a8df6SVlad Buslov 	spin_unlock_bh(&d->tcf_lock);
2686d7a8df6SVlad Buslov 	if (params_new)
2696d7a8df6SVlad Buslov 		kfree_rcu(params_new, rcu);
270ec7727bbSDavide Caratti 	if (goto_ch)
271ec7727bbSDavide Caratti 		tcf_chain_put_by_act(goto_ch);
272ca9b0e27SAlexander Duyck 
273ca9b0e27SAlexander Duyck 	return ret;
274ec7727bbSDavide Caratti put_chain:
275ec7727bbSDavide Caratti 	if (goto_ch)
276ec7727bbSDavide Caratti 		tcf_chain_put_by_act(goto_ch);
277ec7727bbSDavide Caratti release_idr:
278ec7727bbSDavide Caratti 	tcf_idr_release(*a, bind);
279ec7727bbSDavide Caratti 	return err;
280ca9b0e27SAlexander Duyck }
281ca9b0e27SAlexander Duyck 
282cc7ec456SEric Dumazet static int tcf_skbedit_dump(struct sk_buff *skb, struct tc_action *a,
283ca9b0e27SAlexander Duyck 			    int bind, int ref)
284ca9b0e27SAlexander Duyck {
285ca9b0e27SAlexander Duyck 	unsigned char *b = skb_tail_pointer(skb);
286a85a970aSWANG Cong 	struct tcf_skbedit *d = to_skbedit(a);
287c749cddaSDavide Caratti 	struct tcf_skbedit_params *params;
2881c40be12SEric Dumazet 	struct tc_skbedit opt = {
2891c40be12SEric Dumazet 		.index   = d->tcf_index,
290036bb443SVlad Buslov 		.refcnt  = refcount_read(&d->tcf_refcnt) - ref,
291036bb443SVlad Buslov 		.bindcnt = atomic_read(&d->tcf_bindcnt) - bind,
2921c40be12SEric Dumazet 	};
293e7e3728bSQiaobin Fu 	u64 pure_flags = 0;
294c749cddaSDavide Caratti 	struct tcf_t t;
295c749cddaSDavide Caratti 
2966d7a8df6SVlad Buslov 	spin_lock_bh(&d->tcf_lock);
2976d7a8df6SVlad Buslov 	params = rcu_dereference_protected(d->params,
2986d7a8df6SVlad Buslov 					   lockdep_is_held(&d->tcf_lock));
2996d7a8df6SVlad Buslov 	opt.action = d->tcf_action;
300ca9b0e27SAlexander Duyck 
3011b34ec43SDavid S. Miller 	if (nla_put(skb, TCA_SKBEDIT_PARMS, sizeof(opt), &opt))
3021b34ec43SDavid S. Miller 		goto nla_put_failure;
303c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_PRIORITY) &&
304c749cddaSDavide Caratti 	    nla_put_u32(skb, TCA_SKBEDIT_PRIORITY, params->priority))
3051b34ec43SDavid S. Miller 		goto nla_put_failure;
306c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_QUEUE_MAPPING) &&
307c749cddaSDavide Caratti 	    nla_put_u16(skb, TCA_SKBEDIT_QUEUE_MAPPING, params->queue_mapping))
3081b34ec43SDavid S. Miller 		goto nla_put_failure;
309c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_MARK) &&
310c749cddaSDavide Caratti 	    nla_put_u32(skb, TCA_SKBEDIT_MARK, params->mark))
3111b34ec43SDavid S. Miller 		goto nla_put_failure;
312c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_PTYPE) &&
313c749cddaSDavide Caratti 	    nla_put_u16(skb, TCA_SKBEDIT_PTYPE, params->ptype))
314ff202ee1SJamal Hadi Salim 		goto nla_put_failure;
315c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_MASK) &&
316c749cddaSDavide Caratti 	    nla_put_u32(skb, TCA_SKBEDIT_MASK, params->mask))
3174fe77d82SAntonio Quartulli 		goto nla_put_failure;
318c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_INHERITDSFIELD)
319e7e3728bSQiaobin Fu 		pure_flags |= SKBEDIT_F_INHERITDSFIELD;
32038a6f086STonghao Zhang 	if (params->flags & SKBEDIT_F_TXQ_SKBHASH) {
32138a6f086STonghao Zhang 		if (nla_put_u16(skb, TCA_SKBEDIT_QUEUE_MAPPING_MAX,
32238a6f086STonghao Zhang 				params->queue_mapping + params->mapping_mod - 1))
32338a6f086STonghao Zhang 			goto nla_put_failure;
32438a6f086STonghao Zhang 
32538a6f086STonghao Zhang 		pure_flags |= SKBEDIT_F_TXQ_SKBHASH;
32638a6f086STonghao Zhang 	}
327e7e3728bSQiaobin Fu 	if (pure_flags != 0 &&
328e7e3728bSQiaobin Fu 	    nla_put(skb, TCA_SKBEDIT_FLAGS, sizeof(pure_flags), &pure_flags))
329e7e3728bSQiaobin Fu 		goto nla_put_failure;
33048d8ee16SJamal Hadi Salim 
33148d8ee16SJamal Hadi Salim 	tcf_tm_dump(&t, &d->tcf_tm);
3329854518eSNicolas Dichtel 	if (nla_put_64bit(skb, TCA_SKBEDIT_TM, sizeof(t), &t, TCA_SKBEDIT_PAD))
3331b34ec43SDavid S. Miller 		goto nla_put_failure;
3346d7a8df6SVlad Buslov 	spin_unlock_bh(&d->tcf_lock);
3356d7a8df6SVlad Buslov 
336ca9b0e27SAlexander Duyck 	return skb->len;
337ca9b0e27SAlexander Duyck 
338ca9b0e27SAlexander Duyck nla_put_failure:
3396d7a8df6SVlad Buslov 	spin_unlock_bh(&d->tcf_lock);
340ca9b0e27SAlexander Duyck 	nlmsg_trim(skb, b);
341ca9b0e27SAlexander Duyck 	return -1;
342ca9b0e27SAlexander Duyck }
343ca9b0e27SAlexander Duyck 
344c749cddaSDavide Caratti static void tcf_skbedit_cleanup(struct tc_action *a)
345c749cddaSDavide Caratti {
346c749cddaSDavide Caratti 	struct tcf_skbedit *d = to_skbedit(a);
347c749cddaSDavide Caratti 	struct tcf_skbedit_params *params;
348c749cddaSDavide Caratti 
349c749cddaSDavide Caratti 	params = rcu_dereference_protected(d->params, 1);
350c749cddaSDavide Caratti 	if (params)
351c749cddaSDavide Caratti 		kfree_rcu(params, rcu);
352c749cddaSDavide Caratti }
353c749cddaSDavide Caratti 
354e1fea322SRoman Mashak static size_t tcf_skbedit_get_fill_size(const struct tc_action *act)
355e1fea322SRoman Mashak {
356e1fea322SRoman Mashak 	return nla_total_size(sizeof(struct tc_skbedit))
357e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u32)) /* TCA_SKBEDIT_PRIORITY */
358e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u16)) /* TCA_SKBEDIT_QUEUE_MAPPING */
35938a6f086STonghao Zhang 		+ nla_total_size(sizeof(u16)) /* TCA_SKBEDIT_QUEUE_MAPPING_MAX */
360e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u32)) /* TCA_SKBEDIT_MARK */
361e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u16)) /* TCA_SKBEDIT_PTYPE */
362e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u32)) /* TCA_SKBEDIT_MASK */
363e1fea322SRoman Mashak 		+ nla_total_size_64bit(sizeof(u64)); /* TCA_SKBEDIT_FLAGS */
364e1fea322SRoman Mashak }
365e1fea322SRoman Mashak 
366c54e1d92SBaowen Zheng static int tcf_skbedit_offload_act_setup(struct tc_action *act, void *entry_data,
367c2ccf84eSIdo Schimmel 					 u32 *index_inc, bool bind,
368c2ccf84eSIdo Schimmel 					 struct netlink_ext_ack *extack)
369c54e1d92SBaowen Zheng {
370c54e1d92SBaowen Zheng 	if (bind) {
371c54e1d92SBaowen Zheng 		struct flow_action_entry *entry = entry_data;
372c54e1d92SBaowen Zheng 
373c54e1d92SBaowen Zheng 		if (is_tcf_skbedit_mark(act)) {
374c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_MARK;
375c54e1d92SBaowen Zheng 			entry->mark = tcf_skbedit_mark(act);
376c54e1d92SBaowen Zheng 		} else if (is_tcf_skbedit_ptype(act)) {
377c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_PTYPE;
378c54e1d92SBaowen Zheng 			entry->ptype = tcf_skbedit_ptype(act);
379c54e1d92SBaowen Zheng 		} else if (is_tcf_skbedit_priority(act)) {
380c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_PRIORITY;
381c54e1d92SBaowen Zheng 			entry->priority = tcf_skbedit_priority(act);
382*4a6a676fSAmritha Nambiar 		} else if (is_tcf_skbedit_tx_queue_mapping(act)) {
383*4a6a676fSAmritha Nambiar 			NL_SET_ERR_MSG_MOD(extack, "Offload not supported when \"queue_mapping\" option is used on transmit side");
384a9c64939SIdo Schimmel 			return -EOPNOTSUPP;
385*4a6a676fSAmritha Nambiar 		} else if (is_tcf_skbedit_rx_queue_mapping(act)) {
386*4a6a676fSAmritha Nambiar 			entry->id = FLOW_ACTION_RX_QUEUE_MAPPING;
387*4a6a676fSAmritha Nambiar 			entry->rx_queue = tcf_skbedit_rx_queue_mapping(act);
388a9c64939SIdo Schimmel 		} else if (is_tcf_skbedit_inheritdsfield(act)) {
389a9c64939SIdo Schimmel 			NL_SET_ERR_MSG_MOD(extack, "Offload not supported when \"inheritdsfield\" option is used");
390a9c64939SIdo Schimmel 			return -EOPNOTSUPP;
391c54e1d92SBaowen Zheng 		} else {
392a9c64939SIdo Schimmel 			NL_SET_ERR_MSG_MOD(extack, "Unsupported skbedit option offload");
393c54e1d92SBaowen Zheng 			return -EOPNOTSUPP;
394c54e1d92SBaowen Zheng 		}
395c54e1d92SBaowen Zheng 		*index_inc = 1;
396c54e1d92SBaowen Zheng 	} else {
3978cbfe939SBaowen Zheng 		struct flow_offload_action *fl_action = entry_data;
3988cbfe939SBaowen Zheng 
3998cbfe939SBaowen Zheng 		if (is_tcf_skbedit_mark(act))
4008cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_MARK;
4018cbfe939SBaowen Zheng 		else if (is_tcf_skbedit_ptype(act))
4028cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_PTYPE;
4038cbfe939SBaowen Zheng 		else if (is_tcf_skbedit_priority(act))
4048cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_PRIORITY;
405*4a6a676fSAmritha Nambiar 		else if (is_tcf_skbedit_rx_queue_mapping(act))
406*4a6a676fSAmritha Nambiar 			fl_action->id = FLOW_ACTION_RX_QUEUE_MAPPING;
4078cbfe939SBaowen Zheng 		else
408c54e1d92SBaowen Zheng 			return -EOPNOTSUPP;
409c54e1d92SBaowen Zheng 	}
410c54e1d92SBaowen Zheng 
411c54e1d92SBaowen Zheng 	return 0;
412c54e1d92SBaowen Zheng }
413c54e1d92SBaowen Zheng 
414ca9b0e27SAlexander Duyck static struct tc_action_ops act_skbedit_ops = {
415ca9b0e27SAlexander Duyck 	.kind		=	"skbedit",
416eddd2cf1SEli Cohen 	.id		=	TCA_ID_SKBEDIT,
417ca9b0e27SAlexander Duyck 	.owner		=	THIS_MODULE,
41845da1dacSJamal Hadi Salim 	.act		=	tcf_skbedit_act,
419837cb17dSPetr Machata 	.stats_update	=	tcf_skbedit_stats_update,
420ca9b0e27SAlexander Duyck 	.dump		=	tcf_skbedit_dump,
421ca9b0e27SAlexander Duyck 	.init		=	tcf_skbedit_init,
422c749cddaSDavide Caratti 	.cleanup	=	tcf_skbedit_cleanup,
423e1fea322SRoman Mashak 	.get_fill_size	=	tcf_skbedit_get_fill_size,
424c54e1d92SBaowen Zheng 	.offload_act_setup =	tcf_skbedit_offload_act_setup,
425a85a970aSWANG Cong 	.size		=	sizeof(struct tcf_skbedit),
426ddf97ccdSWANG Cong };
427ddf97ccdSWANG Cong 
428ddf97ccdSWANG Cong static __net_init int skbedit_init_net(struct net *net)
429ddf97ccdSWANG Cong {
430acd0a7abSZhengchao Shao 	struct tc_action_net *tn = net_generic(net, act_skbedit_ops.net_id);
431ddf97ccdSWANG Cong 
432981471bdSCong Wang 	return tc_action_net_init(net, tn, &act_skbedit_ops);
433ddf97ccdSWANG Cong }
434ddf97ccdSWANG Cong 
435039af9c6SCong Wang static void __net_exit skbedit_exit_net(struct list_head *net_list)
436ddf97ccdSWANG Cong {
437acd0a7abSZhengchao Shao 	tc_action_net_exit(net_list, act_skbedit_ops.net_id);
438ddf97ccdSWANG Cong }
439ddf97ccdSWANG Cong 
440ddf97ccdSWANG Cong static struct pernet_operations skbedit_net_ops = {
441ddf97ccdSWANG Cong 	.init = skbedit_init_net,
442039af9c6SCong Wang 	.exit_batch = skbedit_exit_net,
443acd0a7abSZhengchao Shao 	.id   = &act_skbedit_ops.net_id,
444ddf97ccdSWANG Cong 	.size = sizeof(struct tc_action_net),
445ca9b0e27SAlexander Duyck };
446ca9b0e27SAlexander Duyck 
447ca9b0e27SAlexander Duyck MODULE_AUTHOR("Alexander Duyck, <alexander.h.duyck@intel.com>");
448ca9b0e27SAlexander Duyck MODULE_DESCRIPTION("SKB Editing");
449ca9b0e27SAlexander Duyck MODULE_LICENSE("GPL");
450ca9b0e27SAlexander Duyck 
451ca9b0e27SAlexander Duyck static int __init skbedit_init_module(void)
452ca9b0e27SAlexander Duyck {
453ddf97ccdSWANG Cong 	return tcf_register_action(&act_skbedit_ops, &skbedit_net_ops);
454ca9b0e27SAlexander Duyck }
455ca9b0e27SAlexander Duyck 
456ca9b0e27SAlexander Duyck static void __exit skbedit_cleanup_module(void)
457ca9b0e27SAlexander Duyck {
458ddf97ccdSWANG Cong 	tcf_unregister_action(&act_skbedit_ops, &skbedit_net_ops);
459ca9b0e27SAlexander Duyck }
460ca9b0e27SAlexander Duyck 
461ca9b0e27SAlexander Duyck module_init(skbedit_init_module);
462ca9b0e27SAlexander Duyck module_exit(skbedit_cleanup_module);
463