xref: /linux/net/sched/act_skbedit.c (revision 79790b6818e96c58fe2bffee1b418c16e64e7b80)
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>
19871cf386SPedro Tammela #include <net/tc_wrapper.h>
20ca9b0e27SAlexander Duyck 
21ca9b0e27SAlexander Duyck #include <linux/tc_act/tc_skbedit.h>
22ca9b0e27SAlexander Duyck #include <net/tc_act/tc_skbedit.h>
23ca9b0e27SAlexander Duyck 
24a85a970aSWANG Cong static struct tc_action_ops act_skbedit_ops;
25ddf97ccdSWANG Cong 
tcf_skbedit_hash(struct tcf_skbedit_params * params,struct sk_buff * skb)2638a6f086STonghao Zhang static u16 tcf_skbedit_hash(struct tcf_skbedit_params *params,
2738a6f086STonghao Zhang 			    struct sk_buff *skb)
2838a6f086STonghao Zhang {
2938a6f086STonghao Zhang 	u16 queue_mapping = params->queue_mapping;
3038a6f086STonghao Zhang 
3138a6f086STonghao Zhang 	if (params->flags & SKBEDIT_F_TXQ_SKBHASH) {
3238a6f086STonghao Zhang 		u32 hash = skb_get_hash(skb);
3338a6f086STonghao Zhang 
3438a6f086STonghao Zhang 		queue_mapping += hash % params->mapping_mod;
3538a6f086STonghao Zhang 	}
3638a6f086STonghao Zhang 
3738a6f086STonghao Zhang 	return netdev_cap_txqueue(skb->dev, queue_mapping);
3838a6f086STonghao Zhang }
3938a6f086STonghao Zhang 
tcf_skbedit_act(struct sk_buff * skb,const struct tc_action * a,struct tcf_result * res)40871cf386SPedro Tammela TC_INDIRECT_SCOPE int tcf_skbedit_act(struct sk_buff *skb,
41871cf386SPedro Tammela 				      const struct tc_action *a,
42ca9b0e27SAlexander Duyck 				      struct tcf_result *res)
43ca9b0e27SAlexander Duyck {
44a85a970aSWANG Cong 	struct tcf_skbedit *d = to_skbedit(a);
45c749cddaSDavide Caratti 	struct tcf_skbedit_params *params;
46c749cddaSDavide Caratti 	int action;
47ca9b0e27SAlexander Duyck 
489c4a4e48SJamal Hadi Salim 	tcf_lastuse_update(&d->tcf_tm);
4950dc9a85SAhmed S. Darwish 	bstats_update(this_cpu_ptr(d->common.cpu_bstats), skb);
50ca9b0e27SAlexander Duyck 
517fd4b288SPaolo Abeni 	params = rcu_dereference_bh(d->params);
52c749cddaSDavide Caratti 	action = READ_ONCE(d->tcf_action);
53c749cddaSDavide Caratti 
54c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_PRIORITY)
55c749cddaSDavide Caratti 		skb->priority = params->priority;
56c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_INHERITDSFIELD) {
57e7e3728bSQiaobin Fu 		int wlen = skb_network_offset(skb);
58e7e3728bSQiaobin Fu 
59d7bf2ebeSToke Høiland-Jørgensen 		switch (skb_protocol(skb, true)) {
60e7e3728bSQiaobin Fu 		case htons(ETH_P_IP):
61e7e3728bSQiaobin Fu 			wlen += sizeof(struct iphdr);
62e7e3728bSQiaobin Fu 			if (!pskb_may_pull(skb, wlen))
63e7e3728bSQiaobin Fu 				goto err;
64e7e3728bSQiaobin Fu 			skb->priority = ipv4_get_dsfield(ip_hdr(skb)) >> 2;
65e7e3728bSQiaobin Fu 			break;
66e7e3728bSQiaobin Fu 
67e7e3728bSQiaobin Fu 		case htons(ETH_P_IPV6):
68e7e3728bSQiaobin Fu 			wlen += sizeof(struct ipv6hdr);
69e7e3728bSQiaobin Fu 			if (!pskb_may_pull(skb, wlen))
70e7e3728bSQiaobin Fu 				goto err;
71e7e3728bSQiaobin Fu 			skb->priority = ipv6_get_dsfield(ipv6_hdr(skb)) >> 2;
72e7e3728bSQiaobin Fu 			break;
73e7e3728bSQiaobin Fu 		}
74e7e3728bSQiaobin Fu 	}
75c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_QUEUE_MAPPING &&
762f1e85b1STonghao Zhang 	    skb->dev->real_num_tx_queues > params->queue_mapping) {
772f1e85b1STonghao Zhang #ifdef CONFIG_NET_EGRESS
782f1e85b1STonghao Zhang 		netdev_xmit_skip_txqueue(true);
792f1e85b1STonghao Zhang #endif
8038a6f086STonghao Zhang 		skb_set_queue_mapping(skb, tcf_skbedit_hash(params, skb));
812f1e85b1STonghao Zhang 	}
82c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_MARK) {
83c749cddaSDavide Caratti 		skb->mark &= ~params->mask;
84c749cddaSDavide Caratti 		skb->mark |= params->mark & params->mask;
854fe77d82SAntonio Quartulli 	}
86c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_PTYPE)
87c749cddaSDavide Caratti 		skb->pkt_type = params->ptype;
88c749cddaSDavide Caratti 	return action;
897fd4b288SPaolo Abeni 
90e7e3728bSQiaobin Fu err:
916f3dfb0dSDavide Caratti 	qstats_drop_inc(this_cpu_ptr(d->common.cpu_qstats));
927fd4b288SPaolo Abeni 	return TC_ACT_SHOT;
93ca9b0e27SAlexander Duyck }
94ca9b0e27SAlexander Duyck 
tcf_skbedit_stats_update(struct tc_action * a,u64 bytes,u64 packets,u64 drops,u64 lastuse,bool hw)95837cb17dSPetr Machata static void tcf_skbedit_stats_update(struct tc_action *a, u64 bytes,
964b61d3e8SPo Liu 				     u64 packets, u64 drops,
974b61d3e8SPo Liu 				     u64 lastuse, bool hw)
98837cb17dSPetr Machata {
99837cb17dSPetr Machata 	struct tcf_skbedit *d = to_skbedit(a);
100837cb17dSPetr Machata 	struct tcf_t *tm = &d->tcf_tm;
101837cb17dSPetr Machata 
1024b61d3e8SPo Liu 	tcf_action_update_stats(a, bytes, packets, drops, hw);
103837cb17dSPetr Machata 	tm->lastuse = max_t(u64, tm->lastuse, lastuse);
104837cb17dSPetr Machata }
105837cb17dSPetr Machata 
106ca9b0e27SAlexander Duyck static const struct nla_policy skbedit_policy[TCA_SKBEDIT_MAX + 1] = {
107ca9b0e27SAlexander Duyck 	[TCA_SKBEDIT_PARMS]		= { .len = sizeof(struct tc_skbedit) },
108ca9b0e27SAlexander Duyck 	[TCA_SKBEDIT_PRIORITY]		= { .len = sizeof(u32) },
109ca9b0e27SAlexander Duyck 	[TCA_SKBEDIT_QUEUE_MAPPING]	= { .len = sizeof(u16) },
1101c55d62eSjamal 	[TCA_SKBEDIT_MARK]		= { .len = sizeof(u32) },
111ff202ee1SJamal Hadi Salim 	[TCA_SKBEDIT_PTYPE]		= { .len = sizeof(u16) },
1124fe77d82SAntonio Quartulli 	[TCA_SKBEDIT_MASK]		= { .len = sizeof(u32) },
113e7e3728bSQiaobin Fu 	[TCA_SKBEDIT_FLAGS]		= { .len = sizeof(u64) },
11438a6f086STonghao Zhang 	[TCA_SKBEDIT_QUEUE_MAPPING_MAX]	= { .len = sizeof(u16) },
115ca9b0e27SAlexander Duyck };
116ca9b0e27SAlexander Duyck 
tcf_skbedit_init(struct net * net,struct nlattr * nla,struct nlattr * est,struct tc_action ** a,struct tcf_proto * tp,u32 act_flags,struct netlink_ext_ack * extack)117c1b52739SBenjamin LaHaise static int tcf_skbedit_init(struct net *net, struct nlattr *nla,
118a85a970aSWANG Cong 			    struct nlattr *est, struct tc_action **a,
119abbb0d33SVlad Buslov 			    struct tcf_proto *tp, u32 act_flags,
120789871bbSVlad Buslov 			    struct netlink_ext_ack *extack)
121ca9b0e27SAlexander Duyck {
122acd0a7abSZhengchao Shao 	struct tc_action_net *tn = net_generic(net, act_skbedit_ops.net_id);
123695176bfSCong Wang 	bool bind = act_flags & TCA_ACT_FLAGS_BIND;
1246d7a8df6SVlad Buslov 	struct tcf_skbedit_params *params_new;
125ca9b0e27SAlexander Duyck 	struct nlattr *tb[TCA_SKBEDIT_MAX + 1];
126ec7727bbSDavide Caratti 	struct tcf_chain *goto_ch = NULL;
127ca9b0e27SAlexander Duyck 	struct tc_skbedit *parm;
128ca9b0e27SAlexander Duyck 	struct tcf_skbedit *d;
1294fe77d82SAntonio Quartulli 	u32 flags = 0, *priority = NULL, *mark = NULL, *mask = NULL;
130ff202ee1SJamal Hadi Salim 	u16 *queue_mapping = NULL, *ptype = NULL;
13138a6f086STonghao Zhang 	u16 mapping_mod = 1;
132b2313077SWANG Cong 	bool exists = false;
133b2313077SWANG Cong 	int ret = 0, err;
1347be8ef2cSDmytro Linkin 	u32 index;
135ca9b0e27SAlexander Duyck 
136ca9b0e27SAlexander Duyck 	if (nla == NULL)
137ca9b0e27SAlexander Duyck 		return -EINVAL;
138ca9b0e27SAlexander Duyck 
1398cb08174SJohannes Berg 	err = nla_parse_nested_deprecated(tb, TCA_SKBEDIT_MAX, nla,
1408cb08174SJohannes Berg 					  skbedit_policy, NULL);
141ca9b0e27SAlexander Duyck 	if (err < 0)
142ca9b0e27SAlexander Duyck 		return err;
143ca9b0e27SAlexander Duyck 
144ca9b0e27SAlexander Duyck 	if (tb[TCA_SKBEDIT_PARMS] == NULL)
145ca9b0e27SAlexander Duyck 		return -EINVAL;
146ca9b0e27SAlexander Duyck 
147ca9b0e27SAlexander Duyck 	if (tb[TCA_SKBEDIT_PRIORITY] != NULL) {
148ca9b0e27SAlexander Duyck 		flags |= SKBEDIT_F_PRIORITY;
149ca9b0e27SAlexander Duyck 		priority = nla_data(tb[TCA_SKBEDIT_PRIORITY]);
150ca9b0e27SAlexander Duyck 	}
151ca9b0e27SAlexander Duyck 
152ca9b0e27SAlexander Duyck 	if (tb[TCA_SKBEDIT_QUEUE_MAPPING] != NULL) {
1534a6a676fSAmritha Nambiar 		if (is_tcf_skbedit_ingress(act_flags) &&
1544a6a676fSAmritha Nambiar 		    !(act_flags & TCA_ACT_FLAGS_SKIP_SW)) {
1554a6a676fSAmritha Nambiar 			NL_SET_ERR_MSG_MOD(extack, "\"queue_mapping\" option on receive side is hardware only, use skip_sw");
1564a6a676fSAmritha Nambiar 			return -EOPNOTSUPP;
1574a6a676fSAmritha Nambiar 		}
158ca9b0e27SAlexander Duyck 		flags |= SKBEDIT_F_QUEUE_MAPPING;
159ca9b0e27SAlexander Duyck 		queue_mapping = nla_data(tb[TCA_SKBEDIT_QUEUE_MAPPING]);
160ca9b0e27SAlexander Duyck 	}
1611c55d62eSjamal 
162ff202ee1SJamal Hadi Salim 	if (tb[TCA_SKBEDIT_PTYPE] != NULL) {
163ff202ee1SJamal Hadi Salim 		ptype = nla_data(tb[TCA_SKBEDIT_PTYPE]);
164ff202ee1SJamal Hadi Salim 		if (!skb_pkt_type_ok(*ptype))
165ff202ee1SJamal Hadi Salim 			return -EINVAL;
166ff202ee1SJamal Hadi Salim 		flags |= SKBEDIT_F_PTYPE;
167ff202ee1SJamal Hadi Salim 	}
168ff202ee1SJamal Hadi Salim 
1691c55d62eSjamal 	if (tb[TCA_SKBEDIT_MARK] != NULL) {
1701c55d62eSjamal 		flags |= SKBEDIT_F_MARK;
1711c55d62eSjamal 		mark = nla_data(tb[TCA_SKBEDIT_MARK]);
1721c55d62eSjamal 	}
1731c55d62eSjamal 
1744fe77d82SAntonio Quartulli 	if (tb[TCA_SKBEDIT_MASK] != NULL) {
1754fe77d82SAntonio Quartulli 		flags |= SKBEDIT_F_MASK;
1764fe77d82SAntonio Quartulli 		mask = nla_data(tb[TCA_SKBEDIT_MASK]);
1774fe77d82SAntonio Quartulli 	}
1784fe77d82SAntonio Quartulli 
179e7e3728bSQiaobin Fu 	if (tb[TCA_SKBEDIT_FLAGS] != NULL) {
180e7e3728bSQiaobin Fu 		u64 *pure_flags = nla_data(tb[TCA_SKBEDIT_FLAGS]);
181e7e3728bSQiaobin Fu 
18238a6f086STonghao Zhang 		if (*pure_flags & SKBEDIT_F_TXQ_SKBHASH) {
18338a6f086STonghao Zhang 			u16 *queue_mapping_max;
18438a6f086STonghao Zhang 
18538a6f086STonghao Zhang 			if (!tb[TCA_SKBEDIT_QUEUE_MAPPING] ||
18638a6f086STonghao Zhang 			    !tb[TCA_SKBEDIT_QUEUE_MAPPING_MAX]) {
18738a6f086STonghao Zhang 				NL_SET_ERR_MSG_MOD(extack, "Missing required range of queue_mapping.");
18838a6f086STonghao Zhang 				return -EINVAL;
18938a6f086STonghao Zhang 			}
19038a6f086STonghao Zhang 
19138a6f086STonghao Zhang 			queue_mapping_max =
19238a6f086STonghao Zhang 				nla_data(tb[TCA_SKBEDIT_QUEUE_MAPPING_MAX]);
19338a6f086STonghao Zhang 			if (*queue_mapping_max < *queue_mapping) {
19438a6f086STonghao Zhang 				NL_SET_ERR_MSG_MOD(extack, "The range of queue_mapping is invalid, max < min.");
19538a6f086STonghao Zhang 				return -EINVAL;
19638a6f086STonghao Zhang 			}
19738a6f086STonghao Zhang 
19838a6f086STonghao Zhang 			mapping_mod = *queue_mapping_max - *queue_mapping + 1;
19938a6f086STonghao Zhang 			flags |= SKBEDIT_F_TXQ_SKBHASH;
20038a6f086STonghao Zhang 		}
201e7e3728bSQiaobin Fu 		if (*pure_flags & SKBEDIT_F_INHERITDSFIELD)
202e7e3728bSQiaobin Fu 			flags |= SKBEDIT_F_INHERITDSFIELD;
203e7e3728bSQiaobin Fu 	}
204e7e3728bSQiaobin Fu 
205ca9b0e27SAlexander Duyck 	parm = nla_data(tb[TCA_SKBEDIT_PARMS]);
2067be8ef2cSDmytro Linkin 	index = parm->index;
2077be8ef2cSDmytro Linkin 	err = tcf_idr_check_alloc(tn, &index, a, bind);
2080190c1d4SVlad Buslov 	if (err < 0)
2090190c1d4SVlad Buslov 		return err;
2100190c1d4SVlad Buslov 	exists = err;
2115e1567aeSJamal Hadi Salim 	if (exists && bind)
212c2a67de9SPedro Tammela 		return ACT_P_BOUND;
2135e1567aeSJamal Hadi Salim 
2145e1567aeSJamal Hadi Salim 	if (!flags) {
215af5d0184SRoman Mashak 		if (exists)
21665a206c0SChris Mi 			tcf_idr_release(*a, bind);
2170190c1d4SVlad Buslov 		else
2187be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
2195e1567aeSJamal Hadi Salim 		return -EINVAL;
2205e1567aeSJamal Hadi Salim 	}
2215e1567aeSJamal Hadi Salim 
2225e1567aeSJamal Hadi Salim 	if (!exists) {
2237be8ef2cSDmytro Linkin 		ret = tcf_idr_create(tn, index, est, a,
22440bd094dSBaowen Zheng 				     &act_skbedit_ops, bind, true, act_flags);
2250190c1d4SVlad Buslov 		if (ret) {
2267be8ef2cSDmytro Linkin 			tcf_idr_cleanup(tn, index);
22786062033SWANG Cong 			return ret;
2280190c1d4SVlad Buslov 		}
229ca9b0e27SAlexander Duyck 
230a85a970aSWANG Cong 		d = to_skbedit(*a);
231ca9b0e27SAlexander Duyck 		ret = ACT_P_CREATED;
232ca9b0e27SAlexander Duyck 	} else {
233a85a970aSWANG Cong 		d = to_skbedit(*a);
234695176bfSCong Wang 		if (!(act_flags & TCA_ACT_FLAGS_REPLACE)) {
23565a206c0SChris Mi 			tcf_idr_release(*a, bind);
236ca9b0e27SAlexander Duyck 			return -EEXIST;
237ca9b0e27SAlexander Duyck 		}
2384e8ddd7fSVlad Buslov 	}
239ec7727bbSDavide Caratti 	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
240ec7727bbSDavide Caratti 	if (err < 0)
241ec7727bbSDavide Caratti 		goto release_idr;
242ca9b0e27SAlexander Duyck 
243c749cddaSDavide Caratti 	params_new = kzalloc(sizeof(*params_new), GFP_KERNEL);
244c749cddaSDavide Caratti 	if (unlikely(!params_new)) {
245ec7727bbSDavide Caratti 		err = -ENOMEM;
246ec7727bbSDavide Caratti 		goto put_chain;
247c749cddaSDavide Caratti 	}
248c749cddaSDavide Caratti 
249c749cddaSDavide Caratti 	params_new->flags = flags;
250ca9b0e27SAlexander Duyck 	if (flags & SKBEDIT_F_PRIORITY)
251c749cddaSDavide Caratti 		params_new->priority = *priority;
25238a6f086STonghao Zhang 	if (flags & SKBEDIT_F_QUEUE_MAPPING) {
253c749cddaSDavide Caratti 		params_new->queue_mapping = *queue_mapping;
25438a6f086STonghao Zhang 		params_new->mapping_mod = mapping_mod;
25538a6f086STonghao Zhang 	}
2561c55d62eSjamal 	if (flags & SKBEDIT_F_MARK)
257c749cddaSDavide Caratti 		params_new->mark = *mark;
258ff202ee1SJamal Hadi Salim 	if (flags & SKBEDIT_F_PTYPE)
259c749cddaSDavide Caratti 		params_new->ptype = *ptype;
2604fe77d82SAntonio Quartulli 	/* default behaviour is to use all the bits */
261c749cddaSDavide Caratti 	params_new->mask = 0xffffffff;
2624fe77d82SAntonio Quartulli 	if (flags & SKBEDIT_F_MASK)
263c749cddaSDavide Caratti 		params_new->mask = *mask;
2641c55d62eSjamal 
2656d7a8df6SVlad Buslov 	spin_lock_bh(&d->tcf_lock);
266ec7727bbSDavide Caratti 	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
267445d3749SPaul E. McKenney 	params_new = rcu_replace_pointer(d->params, params_new,
2686d7a8df6SVlad Buslov 					 lockdep_is_held(&d->tcf_lock));
2696d7a8df6SVlad Buslov 	spin_unlock_bh(&d->tcf_lock);
2706d7a8df6SVlad Buslov 	if (params_new)
2716d7a8df6SVlad Buslov 		kfree_rcu(params_new, rcu);
272ec7727bbSDavide Caratti 	if (goto_ch)
273ec7727bbSDavide Caratti 		tcf_chain_put_by_act(goto_ch);
274ca9b0e27SAlexander Duyck 
275ca9b0e27SAlexander Duyck 	return ret;
276ec7727bbSDavide Caratti put_chain:
277ec7727bbSDavide Caratti 	if (goto_ch)
278ec7727bbSDavide Caratti 		tcf_chain_put_by_act(goto_ch);
279ec7727bbSDavide Caratti release_idr:
280ec7727bbSDavide Caratti 	tcf_idr_release(*a, bind);
281ec7727bbSDavide Caratti 	return err;
282ca9b0e27SAlexander Duyck }
283ca9b0e27SAlexander Duyck 
tcf_skbedit_dump(struct sk_buff * skb,struct tc_action * a,int bind,int ref)284cc7ec456SEric Dumazet static int tcf_skbedit_dump(struct sk_buff *skb, struct tc_action *a,
285ca9b0e27SAlexander Duyck 			    int bind, int ref)
286ca9b0e27SAlexander Duyck {
287ca9b0e27SAlexander Duyck 	unsigned char *b = skb_tail_pointer(skb);
288a85a970aSWANG Cong 	struct tcf_skbedit *d = to_skbedit(a);
289c749cddaSDavide Caratti 	struct tcf_skbedit_params *params;
2901c40be12SEric Dumazet 	struct tc_skbedit opt = {
2911c40be12SEric Dumazet 		.index   = d->tcf_index,
292036bb443SVlad Buslov 		.refcnt  = refcount_read(&d->tcf_refcnt) - ref,
293036bb443SVlad Buslov 		.bindcnt = atomic_read(&d->tcf_bindcnt) - bind,
2941c40be12SEric Dumazet 	};
295e7e3728bSQiaobin Fu 	u64 pure_flags = 0;
296c749cddaSDavide Caratti 	struct tcf_t t;
297c749cddaSDavide Caratti 
2986d7a8df6SVlad Buslov 	spin_lock_bh(&d->tcf_lock);
2996d7a8df6SVlad Buslov 	params = rcu_dereference_protected(d->params,
3006d7a8df6SVlad Buslov 					   lockdep_is_held(&d->tcf_lock));
3016d7a8df6SVlad Buslov 	opt.action = d->tcf_action;
302ca9b0e27SAlexander Duyck 
3031b34ec43SDavid S. Miller 	if (nla_put(skb, TCA_SKBEDIT_PARMS, sizeof(opt), &opt))
3041b34ec43SDavid S. Miller 		goto nla_put_failure;
305c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_PRIORITY) &&
306c749cddaSDavide Caratti 	    nla_put_u32(skb, TCA_SKBEDIT_PRIORITY, params->priority))
3071b34ec43SDavid S. Miller 		goto nla_put_failure;
308c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_QUEUE_MAPPING) &&
309c749cddaSDavide Caratti 	    nla_put_u16(skb, TCA_SKBEDIT_QUEUE_MAPPING, params->queue_mapping))
3101b34ec43SDavid S. Miller 		goto nla_put_failure;
311c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_MARK) &&
312c749cddaSDavide Caratti 	    nla_put_u32(skb, TCA_SKBEDIT_MARK, params->mark))
3131b34ec43SDavid S. Miller 		goto nla_put_failure;
314c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_PTYPE) &&
315c749cddaSDavide Caratti 	    nla_put_u16(skb, TCA_SKBEDIT_PTYPE, params->ptype))
316ff202ee1SJamal Hadi Salim 		goto nla_put_failure;
317c749cddaSDavide Caratti 	if ((params->flags & SKBEDIT_F_MASK) &&
318c749cddaSDavide Caratti 	    nla_put_u32(skb, TCA_SKBEDIT_MASK, params->mask))
3194fe77d82SAntonio Quartulli 		goto nla_put_failure;
320c749cddaSDavide Caratti 	if (params->flags & SKBEDIT_F_INHERITDSFIELD)
321e7e3728bSQiaobin Fu 		pure_flags |= SKBEDIT_F_INHERITDSFIELD;
32238a6f086STonghao Zhang 	if (params->flags & SKBEDIT_F_TXQ_SKBHASH) {
32338a6f086STonghao Zhang 		if (nla_put_u16(skb, TCA_SKBEDIT_QUEUE_MAPPING_MAX,
32438a6f086STonghao Zhang 				params->queue_mapping + params->mapping_mod - 1))
32538a6f086STonghao Zhang 			goto nla_put_failure;
32638a6f086STonghao Zhang 
32738a6f086STonghao Zhang 		pure_flags |= SKBEDIT_F_TXQ_SKBHASH;
32838a6f086STonghao Zhang 	}
329e7e3728bSQiaobin Fu 	if (pure_flags != 0 &&
330e7e3728bSQiaobin Fu 	    nla_put(skb, TCA_SKBEDIT_FLAGS, sizeof(pure_flags), &pure_flags))
331e7e3728bSQiaobin Fu 		goto nla_put_failure;
33248d8ee16SJamal Hadi Salim 
33348d8ee16SJamal Hadi Salim 	tcf_tm_dump(&t, &d->tcf_tm);
3349854518eSNicolas Dichtel 	if (nla_put_64bit(skb, TCA_SKBEDIT_TM, sizeof(t), &t, TCA_SKBEDIT_PAD))
3351b34ec43SDavid S. Miller 		goto nla_put_failure;
3366d7a8df6SVlad Buslov 	spin_unlock_bh(&d->tcf_lock);
3376d7a8df6SVlad Buslov 
338ca9b0e27SAlexander Duyck 	return skb->len;
339ca9b0e27SAlexander Duyck 
340ca9b0e27SAlexander Duyck nla_put_failure:
3416d7a8df6SVlad Buslov 	spin_unlock_bh(&d->tcf_lock);
342ca9b0e27SAlexander Duyck 	nlmsg_trim(skb, b);
343ca9b0e27SAlexander Duyck 	return -1;
344ca9b0e27SAlexander Duyck }
345ca9b0e27SAlexander Duyck 
tcf_skbedit_cleanup(struct tc_action * a)346c749cddaSDavide Caratti static void tcf_skbedit_cleanup(struct tc_action *a)
347c749cddaSDavide Caratti {
348c749cddaSDavide Caratti 	struct tcf_skbedit *d = to_skbedit(a);
349c749cddaSDavide Caratti 	struct tcf_skbedit_params *params;
350c749cddaSDavide Caratti 
351c749cddaSDavide Caratti 	params = rcu_dereference_protected(d->params, 1);
352c749cddaSDavide Caratti 	if (params)
353c749cddaSDavide Caratti 		kfree_rcu(params, rcu);
354c749cddaSDavide Caratti }
355c749cddaSDavide Caratti 
tcf_skbedit_get_fill_size(const struct tc_action * act)356e1fea322SRoman Mashak static size_t tcf_skbedit_get_fill_size(const struct tc_action *act)
357e1fea322SRoman Mashak {
358e1fea322SRoman Mashak 	return nla_total_size(sizeof(struct tc_skbedit))
359e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u32)) /* TCA_SKBEDIT_PRIORITY */
360e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u16)) /* TCA_SKBEDIT_QUEUE_MAPPING */
36138a6f086STonghao Zhang 		+ nla_total_size(sizeof(u16)) /* TCA_SKBEDIT_QUEUE_MAPPING_MAX */
362e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u32)) /* TCA_SKBEDIT_MARK */
363e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u16)) /* TCA_SKBEDIT_PTYPE */
364e1fea322SRoman Mashak 		+ nla_total_size(sizeof(u32)) /* TCA_SKBEDIT_MASK */
365e1fea322SRoman Mashak 		+ nla_total_size_64bit(sizeof(u64)); /* TCA_SKBEDIT_FLAGS */
366e1fea322SRoman Mashak }
367e1fea322SRoman Mashak 
tcf_skbedit_offload_act_setup(struct tc_action * act,void * entry_data,u32 * index_inc,bool bind,struct netlink_ext_ack * extack)368c54e1d92SBaowen Zheng static int tcf_skbedit_offload_act_setup(struct tc_action *act, void *entry_data,
369c2ccf84eSIdo Schimmel 					 u32 *index_inc, bool bind,
370c2ccf84eSIdo Schimmel 					 struct netlink_ext_ack *extack)
371c54e1d92SBaowen Zheng {
372c54e1d92SBaowen Zheng 	if (bind) {
373c54e1d92SBaowen Zheng 		struct flow_action_entry *entry = entry_data;
374c54e1d92SBaowen Zheng 
375c54e1d92SBaowen Zheng 		if (is_tcf_skbedit_mark(act)) {
376c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_MARK;
377c54e1d92SBaowen Zheng 			entry->mark = tcf_skbedit_mark(act);
378c54e1d92SBaowen Zheng 		} else if (is_tcf_skbedit_ptype(act)) {
379c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_PTYPE;
380c54e1d92SBaowen Zheng 			entry->ptype = tcf_skbedit_ptype(act);
381c54e1d92SBaowen Zheng 		} else if (is_tcf_skbedit_priority(act)) {
382c54e1d92SBaowen Zheng 			entry->id = FLOW_ACTION_PRIORITY;
383c54e1d92SBaowen Zheng 			entry->priority = tcf_skbedit_priority(act);
3844a6a676fSAmritha Nambiar 		} else if (is_tcf_skbedit_tx_queue_mapping(act)) {
3854a6a676fSAmritha Nambiar 			NL_SET_ERR_MSG_MOD(extack, "Offload not supported when \"queue_mapping\" option is used on transmit side");
386a9c64939SIdo Schimmel 			return -EOPNOTSUPP;
3874a6a676fSAmritha Nambiar 		} else if (is_tcf_skbedit_rx_queue_mapping(act)) {
3884a6a676fSAmritha Nambiar 			entry->id = FLOW_ACTION_RX_QUEUE_MAPPING;
3894a6a676fSAmritha Nambiar 			entry->rx_queue = tcf_skbedit_rx_queue_mapping(act);
390a9c64939SIdo Schimmel 		} else if (is_tcf_skbedit_inheritdsfield(act)) {
391a9c64939SIdo Schimmel 			NL_SET_ERR_MSG_MOD(extack, "Offload not supported when \"inheritdsfield\" option is used");
392a9c64939SIdo Schimmel 			return -EOPNOTSUPP;
393c54e1d92SBaowen Zheng 		} else {
394a9c64939SIdo Schimmel 			NL_SET_ERR_MSG_MOD(extack, "Unsupported skbedit option offload");
395c54e1d92SBaowen Zheng 			return -EOPNOTSUPP;
396c54e1d92SBaowen Zheng 		}
397c54e1d92SBaowen Zheng 		*index_inc = 1;
398c54e1d92SBaowen Zheng 	} else {
3998cbfe939SBaowen Zheng 		struct flow_offload_action *fl_action = entry_data;
4008cbfe939SBaowen Zheng 
4018cbfe939SBaowen Zheng 		if (is_tcf_skbedit_mark(act))
4028cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_MARK;
4038cbfe939SBaowen Zheng 		else if (is_tcf_skbedit_ptype(act))
4048cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_PTYPE;
4058cbfe939SBaowen Zheng 		else if (is_tcf_skbedit_priority(act))
4068cbfe939SBaowen Zheng 			fl_action->id = FLOW_ACTION_PRIORITY;
4074a6a676fSAmritha Nambiar 		else if (is_tcf_skbedit_rx_queue_mapping(act))
4084a6a676fSAmritha Nambiar 			fl_action->id = FLOW_ACTION_RX_QUEUE_MAPPING;
4098cbfe939SBaowen Zheng 		else
410c54e1d92SBaowen Zheng 			return -EOPNOTSUPP;
411c54e1d92SBaowen Zheng 	}
412c54e1d92SBaowen Zheng 
413c54e1d92SBaowen Zheng 	return 0;
414c54e1d92SBaowen Zheng }
415c54e1d92SBaowen Zheng 
416ca9b0e27SAlexander Duyck static struct tc_action_ops act_skbedit_ops = {
417ca9b0e27SAlexander Duyck 	.kind		=	"skbedit",
418eddd2cf1SEli Cohen 	.id		=	TCA_ID_SKBEDIT,
419ca9b0e27SAlexander Duyck 	.owner		=	THIS_MODULE,
42045da1dacSJamal Hadi Salim 	.act		=	tcf_skbedit_act,
421837cb17dSPetr Machata 	.stats_update	=	tcf_skbedit_stats_update,
422ca9b0e27SAlexander Duyck 	.dump		=	tcf_skbedit_dump,
423ca9b0e27SAlexander Duyck 	.init		=	tcf_skbedit_init,
424c749cddaSDavide Caratti 	.cleanup	=	tcf_skbedit_cleanup,
425e1fea322SRoman Mashak 	.get_fill_size	=	tcf_skbedit_get_fill_size,
426c54e1d92SBaowen Zheng 	.offload_act_setup =	tcf_skbedit_offload_act_setup,
427a85a970aSWANG Cong 	.size		=	sizeof(struct tcf_skbedit),
428ddf97ccdSWANG Cong };
429*241a94abSMichal Koutný MODULE_ALIAS_NET_ACT("skbedit");
430ddf97ccdSWANG Cong 
skbedit_init_net(struct net * net)431ddf97ccdSWANG Cong static __net_init int skbedit_init_net(struct net *net)
432ddf97ccdSWANG Cong {
433acd0a7abSZhengchao Shao 	struct tc_action_net *tn = net_generic(net, act_skbedit_ops.net_id);
434ddf97ccdSWANG Cong 
435981471bdSCong Wang 	return tc_action_net_init(net, tn, &act_skbedit_ops);
436ddf97ccdSWANG Cong }
437ddf97ccdSWANG Cong 
skbedit_exit_net(struct list_head * net_list)438039af9c6SCong Wang static void __net_exit skbedit_exit_net(struct list_head *net_list)
439ddf97ccdSWANG Cong {
440acd0a7abSZhengchao Shao 	tc_action_net_exit(net_list, act_skbedit_ops.net_id);
441ddf97ccdSWANG Cong }
442ddf97ccdSWANG Cong 
443ddf97ccdSWANG Cong static struct pernet_operations skbedit_net_ops = {
444ddf97ccdSWANG Cong 	.init = skbedit_init_net,
445039af9c6SCong Wang 	.exit_batch = skbedit_exit_net,
446acd0a7abSZhengchao Shao 	.id   = &act_skbedit_ops.net_id,
447ddf97ccdSWANG Cong 	.size = sizeof(struct tc_action_net),
448ca9b0e27SAlexander Duyck };
449ca9b0e27SAlexander Duyck 
450ca9b0e27SAlexander Duyck MODULE_AUTHOR("Alexander Duyck, <alexander.h.duyck@intel.com>");
451ca9b0e27SAlexander Duyck MODULE_DESCRIPTION("SKB Editing");
452ca9b0e27SAlexander Duyck MODULE_LICENSE("GPL");
453ca9b0e27SAlexander Duyck 
skbedit_init_module(void)454ca9b0e27SAlexander Duyck static int __init skbedit_init_module(void)
455ca9b0e27SAlexander Duyck {
456ddf97ccdSWANG Cong 	return tcf_register_action(&act_skbedit_ops, &skbedit_net_ops);
457ca9b0e27SAlexander Duyck }
458ca9b0e27SAlexander Duyck 
skbedit_cleanup_module(void)459ca9b0e27SAlexander Duyck static void __exit skbedit_cleanup_module(void)
460ca9b0e27SAlexander Duyck {
461ddf97ccdSWANG Cong 	tcf_unregister_action(&act_skbedit_ops, &skbedit_net_ops);
462ca9b0e27SAlexander Duyck }
463ca9b0e27SAlexander Duyck 
464ca9b0e27SAlexander Duyck module_init(skbedit_init_module);
465ca9b0e27SAlexander Duyck module_exit(skbedit_cleanup_module);
466