File Index
Files and directories
Sign in
FILE

act_skbmod.c

File preview with syntax highlighting.

linux-6.19.14/net/sched/act_skbmod.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * net/sched/act_skbmod.c  skb data modifier
  4 *
  5 * Copyright (c) 2016 Jamal Hadi Salim <jhs@mojatatu.com>
  6*/
  7
  8#include <linux/module.h>
  9#include <linux/if_arp.h>
 10#include <linux/init.h>
 11#include <linux/kernel.h>
 12#include <linux/skbuff.h>
 13#include <linux/rtnetlink.h>
 14#include <net/inet_ecn.h>
 15#include <net/netlink.h>
 16#include <net/pkt_sched.h>
 17#include <net/pkt_cls.h>
 18#include <net/tc_wrapper.h>
 19
 20#include <linux/tc_act/tc_skbmod.h>
 21#include <net/tc_act/tc_skbmod.h>
 22
 23static struct tc_action_ops act_skbmod_ops;
 24
 25TC_INDIRECT_SCOPE int tcf_skbmod_act(struct sk_buff *skb,
 26				     const struct tc_action *a,
 27				     struct tcf_result *res)
 28{
 29	struct tcf_skbmod *d = to_skbmod(a);
 30	struct tcf_skbmod_params *p;
 31	int max_edit_len, err;
 32	u64 flags;
 33
 34	tcf_lastuse_update(&d->tcf_tm);
 35	bstats_update(this_cpu_ptr(d->common.cpu_bstats), skb);
 36
 37	p = rcu_dereference_bh(d->skbmod_p);
 38	if (unlikely(p->action == TC_ACT_SHOT))
 39		goto drop;
 40
 41	max_edit_len = skb_mac_header_len(skb);
 42	flags = p->flags;
 43
 44	/* tcf_skbmod_init() guarantees "flags" to be one of the following:
 45	 *	1. a combination of SKBMOD_F_{DMAC,SMAC,ETYPE}
 46	 *	2. SKBMOD_F_SWAPMAC
 47	 *	3. SKBMOD_F_ECN
 48	 * SKBMOD_F_ECN only works with IP packets; all other flags only work with Ethernet
 49	 * packets.
 50	 */
 51	if (flags == SKBMOD_F_ECN) {
 52		switch (skb_protocol(skb, true)) {
 53		case cpu_to_be16(ETH_P_IP):
 54		case cpu_to_be16(ETH_P_IPV6):
 55			max_edit_len += skb_network_header_len(skb);
 56			break;
 57		default:
 58			goto out;
 59		}
 60	} else if (!skb->dev || skb->dev->type != ARPHRD_ETHER) {
 61		goto out;
 62	}
 63
 64	err = skb_ensure_writable(skb, max_edit_len);
 65	if (unlikely(err)) /* best policy is to drop on the floor */
 66		goto drop;
 67
 68	if (flags & SKBMOD_F_DMAC)
 69		ether_addr_copy(eth_hdr(skb)->h_dest, p->eth_dst);
 70	if (flags & SKBMOD_F_SMAC)
 71		ether_addr_copy(eth_hdr(skb)->h_source, p->eth_src);
 72	if (flags & SKBMOD_F_ETYPE)
 73		eth_hdr(skb)->h_proto = p->eth_type;
 74
 75	if (flags & SKBMOD_F_SWAPMAC) {
 76		u16 tmpaddr[ETH_ALEN / 2]; /* ether_addr_copy() requirement */
 77		/*XXX: I am sure we can come up with more efficient swapping*/
 78		ether_addr_copy((u8 *)tmpaddr, eth_hdr(skb)->h_dest);
 79		ether_addr_copy(eth_hdr(skb)->h_dest, eth_hdr(skb)->h_source);
 80		ether_addr_copy(eth_hdr(skb)->h_source, (u8 *)tmpaddr);
 81	}
 82
 83	if (flags & SKBMOD_F_ECN)
 84		INET_ECN_set_ce(skb);
 85
 86out:
 87	return p->action;
 88
 89drop:
 90	qstats_overlimit_inc(this_cpu_ptr(d->common.cpu_qstats));
 91	return TC_ACT_SHOT;
 92}
 93
 94static const struct nla_policy skbmod_policy[TCA_SKBMOD_MAX + 1] = {
 95	[TCA_SKBMOD_PARMS]		= { .len = sizeof(struct tc_skbmod) },
 96	[TCA_SKBMOD_DMAC]		= { .len = ETH_ALEN },
 97	[TCA_SKBMOD_SMAC]		= { .len = ETH_ALEN },
 98	[TCA_SKBMOD_ETYPE]		= { .type = NLA_U16 },
 99};
100
101static int tcf_skbmod_init(struct net *net, struct nlattr *nla,
102			   struct nlattr *est, struct tc_action **a,
103			   struct tcf_proto *tp, u32 flags,
104			   struct netlink_ext_ack *extack)
105{
106	struct tc_action_net *tn = net_generic(net, act_skbmod_ops.net_id);
107	bool ovr = flags & TCA_ACT_FLAGS_REPLACE;
108	bool bind = flags & TCA_ACT_FLAGS_BIND;
109	struct nlattr *tb[TCA_SKBMOD_MAX + 1];
110	struct tcf_skbmod_params *p, *p_old;
111	struct tcf_chain *goto_ch = NULL;
112	struct tc_skbmod *parm;
113	u32 lflags = 0, index;
114	struct tcf_skbmod *d;
115	bool exists = false;
116	u8 *daddr = NULL;
117	u8 *saddr = NULL;
118	u16 eth_type = 0;
119	int ret = 0, err;
120
121	if (!nla)
122		return -EINVAL;
123
124	err = nla_parse_nested_deprecated(tb, TCA_SKBMOD_MAX, nla,
125					  skbmod_policy, NULL);
126	if (err < 0)
127		return err;
128
129	if (!tb[TCA_SKBMOD_PARMS])
130		return -EINVAL;
131
132	if (tb[TCA_SKBMOD_DMAC]) {
133		daddr = nla_data(tb[TCA_SKBMOD_DMAC]);
134		lflags |= SKBMOD_F_DMAC;
135	}
136
137	if (tb[TCA_SKBMOD_SMAC]) {
138		saddr = nla_data(tb[TCA_SKBMOD_SMAC]);
139		lflags |= SKBMOD_F_SMAC;
140	}
141
142	if (tb[TCA_SKBMOD_ETYPE]) {
143		eth_type = nla_get_u16(tb[TCA_SKBMOD_ETYPE]);
144		lflags |= SKBMOD_F_ETYPE;
145	}
146
147	parm = nla_data(tb[TCA_SKBMOD_PARMS]);
148	index = parm->index;
149	if (parm->flags & SKBMOD_F_SWAPMAC)
150		lflags = SKBMOD_F_SWAPMAC;
151	if (parm->flags & SKBMOD_F_ECN)
152		lflags = SKBMOD_F_ECN;
153
154	err = tcf_idr_check_alloc(tn, &index, a, bind);
155	if (err < 0)
156		return err;
157	exists = err;
158	if (exists && bind)
159		return ACT_P_BOUND;
160
161	if (!lflags) {
162		if (exists)
163			tcf_idr_release(*a, bind);
164		else
165			tcf_idr_cleanup(tn, index);
166		return -EINVAL;
167	}
168
169	if (!exists) {
170		ret = tcf_idr_create(tn, index, est, a,
171				     &act_skbmod_ops, bind, true, flags);
172		if (ret) {
173			tcf_idr_cleanup(tn, index);
174			return ret;
175		}
176
177		ret = ACT_P_CREATED;
178	} else if (!ovr) {
179		tcf_idr_release(*a, bind);
180		return -EEXIST;
181	}
182	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
183	if (err < 0)
184		goto release_idr;
185
186	d = to_skbmod(*a);
187
188	p = kzalloc(sizeof(struct tcf_skbmod_params), GFP_KERNEL);
189	if (unlikely(!p)) {
190		err = -ENOMEM;
191		goto put_chain;
192	}
193
194	p->flags = lflags;
195	p->action = parm->action;
196	if (ovr)
197		spin_lock_bh(&d->tcf_lock);
198	/* Protected by tcf_lock if overwriting existing action. */
199	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
200	p_old = rcu_dereference_protected(d->skbmod_p, 1);
201
202	if (lflags & SKBMOD_F_DMAC)
203		ether_addr_copy(p->eth_dst, daddr);
204	if (lflags & SKBMOD_F_SMAC)
205		ether_addr_copy(p->eth_src, saddr);
206	if (lflags & SKBMOD_F_ETYPE)
207		p->eth_type = htons(eth_type);
208
209	rcu_assign_pointer(d->skbmod_p, p);
210	if (ovr)
211		spin_unlock_bh(&d->tcf_lock);
212
213	if (p_old)
214		kfree_rcu(p_old, rcu);
215	if (goto_ch)
216		tcf_chain_put_by_act(goto_ch);
217
218	return ret;
219put_chain:
220	if (goto_ch)
221		tcf_chain_put_by_act(goto_ch);
222release_idr:
223	tcf_idr_release(*a, bind);
224	return err;
225}
226
227static void tcf_skbmod_cleanup(struct tc_action *a)
228{
229	struct tcf_skbmod *d = to_skbmod(a);
230	struct tcf_skbmod_params  *p;
231
232	p = rcu_dereference_protected(d->skbmod_p, 1);
233	if (p)
234		kfree_rcu(p, rcu);
235}
236
237static int tcf_skbmod_dump(struct sk_buff *skb, struct tc_action *a,
238			   int bind, int ref)
239{
240	struct tcf_skbmod *d = to_skbmod(a);
241	unsigned char *b = skb_tail_pointer(skb);
242	struct tcf_skbmod_params  *p;
243	struct tc_skbmod opt;
244	struct tcf_t t;
245
246	memset(&opt, 0, sizeof(opt));
247	opt.index   = d->tcf_index;
248	opt.refcnt  = refcount_read(&d->tcf_refcnt) - ref;
249	opt.bindcnt = atomic_read(&d->tcf_bindcnt) - bind;
250	rcu_read_lock();
251	p = rcu_dereference(d->skbmod_p);
252	opt.action = p->action;
253	opt.flags  = p->flags;
254	if (nla_put(skb, TCA_SKBMOD_PARMS, sizeof(opt), &opt))
255		goto nla_put_failure;
256	if ((p->flags & SKBMOD_F_DMAC) &&
257	    nla_put(skb, TCA_SKBMOD_DMAC, ETH_ALEN, p->eth_dst))
258		goto nla_put_failure;
259	if ((p->flags & SKBMOD_F_SMAC) &&
260	    nla_put(skb, TCA_SKBMOD_SMAC, ETH_ALEN, p->eth_src))
261		goto nla_put_failure;
262	if ((p->flags & SKBMOD_F_ETYPE) &&
263	    nla_put_u16(skb, TCA_SKBMOD_ETYPE, ntohs(p->eth_type)))
264		goto nla_put_failure;
265
266	tcf_tm_dump(&t, &d->tcf_tm);
267	if (nla_put_64bit(skb, TCA_SKBMOD_TM, sizeof(t), &t, TCA_SKBMOD_PAD))
268		goto nla_put_failure;
269
270	rcu_read_unlock();
271	return skb->len;
272nla_put_failure:
273	rcu_read_unlock();
274	nlmsg_trim(skb, b);
275	return -1;
276}
277
278static struct tc_action_ops act_skbmod_ops = {
279	.kind		=	"skbmod",
280	.id		=	TCA_ACT_SKBMOD,
281	.owner		=	THIS_MODULE,
282	.act		=	tcf_skbmod_act,
283	.dump		=	tcf_skbmod_dump,
284	.init		=	tcf_skbmod_init,
285	.cleanup	=	tcf_skbmod_cleanup,
286	.size		=	sizeof(struct tcf_skbmod),
287};
288MODULE_ALIAS_NET_ACT("skbmod");
289
290static __net_init int skbmod_init_net(struct net *net)
291{
292	struct tc_action_net *tn = net_generic(net, act_skbmod_ops.net_id);
293
294	return tc_action_net_init(net, tn, &act_skbmod_ops);
295}
296
297static void __net_exit skbmod_exit_net(struct list_head *net_list)
298{
299	tc_action_net_exit(net_list, act_skbmod_ops.net_id);
300}
301
302static struct pernet_operations skbmod_net_ops = {
303	.init = skbmod_init_net,
304	.exit_batch = skbmod_exit_net,
305	.id   = &act_skbmod_ops.net_id,
306	.size = sizeof(struct tc_action_net),
307};
308
309MODULE_AUTHOR("Jamal Hadi Salim, <jhs@mojatatu.com>");
310MODULE_DESCRIPTION("SKB data mod-ing");
311MODULE_LICENSE("GPL");
312
313static int __init skbmod_init_module(void)
314{
315	return tcf_register_action(&act_skbmod_ops, &skbmod_net_ops);
316}
317
318static void __exit skbmod_cleanup_module(void)
319{
320	tcf_unregister_action(&act_skbmod_ops, &skbmod_net_ops);
321}
322
323module_init(skbmod_init_module);
324module_exit(skbmod_cleanup_module);
~/ Read-only file preview.