File Index
Files and directories
Sign in
FILE

act_ctinfo.c

File preview with syntax highlighting.

linux-6.19.14/net/sched/act_ctinfo.c C
  1// SPDX-License-Identifier: GPL-2.0+
  2/* net/sched/act_ctinfo.c  netfilter ctinfo connmark actions
  3 *
  4 * Copyright (c) 2019 Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
  5 */
  6
  7#include <linux/module.h>
  8#include <linux/init.h>
  9#include <linux/kernel.h>
 10#include <linux/skbuff.h>
 11#include <linux/rtnetlink.h>
 12#include <linux/pkt_cls.h>
 13#include <linux/ip.h>
 14#include <linux/ipv6.h>
 15#include <net/netlink.h>
 16#include <net/pkt_sched.h>
 17#include <net/act_api.h>
 18#include <net/pkt_cls.h>
 19#include <uapi/linux/tc_act/tc_ctinfo.h>
 20#include <net/tc_act/tc_ctinfo.h>
 21#include <net/tc_wrapper.h>
 22
 23#include <net/netfilter/nf_conntrack.h>
 24#include <net/netfilter/nf_conntrack_core.h>
 25#include <net/netfilter/nf_conntrack_ecache.h>
 26#include <net/netfilter/nf_conntrack_zones.h>
 27
 28static struct tc_action_ops act_ctinfo_ops;
 29
 30static void tcf_ctinfo_dscp_set(struct nf_conn *ct, struct tcf_ctinfo *ca,
 31				struct tcf_ctinfo_params *cp,
 32				struct sk_buff *skb, int wlen, int proto)
 33{
 34	u8 dscp, newdscp;
 35
 36	newdscp = (((READ_ONCE(ct->mark) & cp->dscpmask) >> cp->dscpmaskshift) << 2) &
 37		     ~INET_ECN_MASK;
 38
 39	switch (proto) {
 40	case NFPROTO_IPV4:
 41		dscp = ipv4_get_dsfield(ip_hdr(skb)) & ~INET_ECN_MASK;
 42		if (dscp != newdscp) {
 43			if (likely(!skb_try_make_writable(skb, wlen))) {
 44				ipv4_change_dsfield(ip_hdr(skb),
 45						    INET_ECN_MASK,
 46						    newdscp);
 47				atomic64_inc(&ca->stats_dscp_set);
 48			} else {
 49				atomic64_inc(&ca->stats_dscp_error);
 50			}
 51		}
 52		break;
 53	case NFPROTO_IPV6:
 54		dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & ~INET_ECN_MASK;
 55		if (dscp != newdscp) {
 56			if (likely(!skb_try_make_writable(skb, wlen))) {
 57				ipv6_change_dsfield(ipv6_hdr(skb),
 58						    INET_ECN_MASK,
 59						    newdscp);
 60				atomic64_inc(&ca->stats_dscp_set);
 61			} else {
 62				atomic64_inc(&ca->stats_dscp_error);
 63			}
 64		}
 65		break;
 66	default:
 67		break;
 68	}
 69}
 70
 71static void tcf_ctinfo_cpmark_set(struct nf_conn *ct, struct tcf_ctinfo *ca,
 72				  struct tcf_ctinfo_params *cp,
 73				  struct sk_buff *skb)
 74{
 75	atomic64_inc(&ca->stats_cpmark_set);
 76	skb->mark = READ_ONCE(ct->mark) & cp->cpmarkmask;
 77}
 78
 79TC_INDIRECT_SCOPE int tcf_ctinfo_act(struct sk_buff *skb,
 80				     const struct tc_action *a,
 81				     struct tcf_result *res)
 82{
 83	const struct nf_conntrack_tuple_hash *thash = NULL;
 84	struct tcf_ctinfo *ca = to_ctinfo(a);
 85	struct nf_conntrack_tuple tuple;
 86	struct nf_conntrack_zone zone;
 87	enum ip_conntrack_info ctinfo;
 88	struct tcf_ctinfo_params *cp;
 89	struct nf_conn *ct;
 90	int proto, wlen;
 91
 92	cp = rcu_dereference_bh(ca->params);
 93
 94	tcf_lastuse_update(&ca->tcf_tm);
 95	tcf_action_update_bstats(&ca->common, skb);
 96
 97	wlen = skb_network_offset(skb);
 98	switch (skb_protocol(skb, true)) {
 99	case htons(ETH_P_IP):
100		wlen += sizeof(struct iphdr);
101		if (!pskb_may_pull(skb, wlen))
102			goto out;
103
104		proto = NFPROTO_IPV4;
105		break;
106	case htons(ETH_P_IPV6):
107		wlen += sizeof(struct ipv6hdr);
108		if (!pskb_may_pull(skb, wlen))
109			goto out;
110
111		proto = NFPROTO_IPV6;
112		break;
113	default:
114		goto out;
115	}
116
117	ct = nf_ct_get(skb, &ctinfo);
118	if (!ct) { /* look harder, usually ingress */
119		if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
120				       proto, cp->net, &tuple))
121			goto out;
122		zone.id = cp->zone;
123		zone.dir = NF_CT_DEFAULT_ZONE_DIR;
124
125		thash = nf_conntrack_find_get(cp->net, &zone, &tuple);
126		if (!thash)
127			goto out;
128
129		ct = nf_ct_tuplehash_to_ctrack(thash);
130	}
131
132	if (cp->mode & CTINFO_MODE_DSCP)
133		if (!cp->dscpstatemask || (READ_ONCE(ct->mark) & cp->dscpstatemask))
134			tcf_ctinfo_dscp_set(ct, ca, cp, skb, wlen, proto);
135
136	if (cp->mode & CTINFO_MODE_CPMARK)
137		tcf_ctinfo_cpmark_set(ct, ca, cp, skb);
138
139	if (thash)
140		nf_ct_put(ct);
141out:
142	return cp->action;
143}
144
145static const struct nla_policy ctinfo_policy[TCA_CTINFO_MAX + 1] = {
146	[TCA_CTINFO_ACT]		  =
147		NLA_POLICY_EXACT_LEN(sizeof(struct tc_ctinfo)),
148	[TCA_CTINFO_ZONE]		  = { .type = NLA_U16 },
149	[TCA_CTINFO_PARMS_DSCP_MASK]	  = { .type = NLA_U32 },
150	[TCA_CTINFO_PARMS_DSCP_STATEMASK] = { .type = NLA_U32 },
151	[TCA_CTINFO_PARMS_CPMARK_MASK]	  = { .type = NLA_U32 },
152};
153
154static int tcf_ctinfo_init(struct net *net, struct nlattr *nla,
155			   struct nlattr *est, struct tc_action **a,
156			   struct tcf_proto *tp, u32 flags,
157			   struct netlink_ext_ack *extack)
158{
159	struct tc_action_net *tn = net_generic(net, act_ctinfo_ops.net_id);
160	bool bind = flags & TCA_ACT_FLAGS_BIND;
161	u32 dscpmask = 0, dscpstatemask, index;
162	struct nlattr *tb[TCA_CTINFO_MAX + 1];
163	struct tcf_ctinfo_params *cp_new;
164	struct tcf_chain *goto_ch = NULL;
165	struct tc_ctinfo *actparm;
166	struct tcf_ctinfo *ci;
167	u8 dscpmaskshift;
168	int ret = 0, err;
169
170	if (!nla) {
171		NL_SET_ERR_MSG_MOD(extack, "ctinfo requires attributes to be passed");
172		return -EINVAL;
173	}
174
175	err = nla_parse_nested(tb, TCA_CTINFO_MAX, nla, ctinfo_policy, extack);
176	if (err < 0)
177		return err;
178
179	if (!tb[TCA_CTINFO_ACT]) {
180		NL_SET_ERR_MSG_MOD(extack,
181				   "Missing required TCA_CTINFO_ACT attribute");
182		return -EINVAL;
183	}
184	actparm = nla_data(tb[TCA_CTINFO_ACT]);
185
186	/* do some basic validation here before dynamically allocating things */
187	/* that we would otherwise have to clean up.			      */
188	if (tb[TCA_CTINFO_PARMS_DSCP_MASK]) {
189		dscpmask = nla_get_u32(tb[TCA_CTINFO_PARMS_DSCP_MASK]);
190		/* need contiguous 6 bit mask */
191		dscpmaskshift = dscpmask ? __ffs(dscpmask) : 0;
192		if ((~0 & (dscpmask >> dscpmaskshift)) != 0x3f) {
193			NL_SET_ERR_MSG_ATTR(extack,
194					    tb[TCA_CTINFO_PARMS_DSCP_MASK],
195					    "dscp mask must be 6 contiguous bits");
196			return -EINVAL;
197		}
198		dscpstatemask =
199			nla_get_u32_default(tb[TCA_CTINFO_PARMS_DSCP_STATEMASK],
200					    0);
201		/* mask & statemask must not overlap */
202		if (dscpmask & dscpstatemask) {
203			NL_SET_ERR_MSG_ATTR(extack,
204					    tb[TCA_CTINFO_PARMS_DSCP_STATEMASK],
205					    "dscp statemask must not overlap dscp mask");
206			return -EINVAL;
207		}
208	}
209
210	/* done the validation:now to the actual action allocation */
211	index = actparm->index;
212	err = tcf_idr_check_alloc(tn, &index, a, bind);
213	if (!err) {
214		ret = tcf_idr_create_from_flags(tn, index, est, a,
215						&act_ctinfo_ops, bind, flags);
216		if (ret) {
217			tcf_idr_cleanup(tn, index);
218			return ret;
219		}
220		ret = ACT_P_CREATED;
221	} else if (err > 0) {
222		if (bind) /* don't override defaults */
223			return ACT_P_BOUND;
224		if (!(flags & TCA_ACT_FLAGS_REPLACE)) {
225			tcf_idr_release(*a, bind);
226			return -EEXIST;
227		}
228	} else {
229		return err;
230	}
231
232	err = tcf_action_check_ctrlact(actparm->action, tp, &goto_ch, extack);
233	if (err < 0)
234		goto release_idr;
235
236	ci = to_ctinfo(*a);
237
238	cp_new = kzalloc(sizeof(*cp_new), GFP_KERNEL);
239	if (unlikely(!cp_new)) {
240		err = -ENOMEM;
241		goto put_chain;
242	}
243
244	cp_new->net = net;
245	cp_new->zone = nla_get_u16_default(tb[TCA_CTINFO_ZONE], 0);
246	if (dscpmask) {
247		cp_new->dscpmask = dscpmask;
248		cp_new->dscpmaskshift = dscpmaskshift;
249		cp_new->dscpstatemask = dscpstatemask;
250		cp_new->mode |= CTINFO_MODE_DSCP;
251	}
252
253	if (tb[TCA_CTINFO_PARMS_CPMARK_MASK]) {
254		cp_new->cpmarkmask =
255				nla_get_u32(tb[TCA_CTINFO_PARMS_CPMARK_MASK]);
256		cp_new->mode |= CTINFO_MODE_CPMARK;
257	}
258
259	cp_new->action = actparm->action;
260
261	spin_lock_bh(&ci->tcf_lock);
262	goto_ch = tcf_action_set_ctrlact(*a, actparm->action, goto_ch);
263	cp_new = rcu_replace_pointer(ci->params, cp_new,
264				     lockdep_is_held(&ci->tcf_lock));
265	spin_unlock_bh(&ci->tcf_lock);
266
267	if (goto_ch)
268		tcf_chain_put_by_act(goto_ch);
269	if (cp_new)
270		kfree_rcu(cp_new, rcu);
271
272	return ret;
273
274put_chain:
275	if (goto_ch)
276		tcf_chain_put_by_act(goto_ch);
277release_idr:
278	tcf_idr_release(*a, bind);
279	return err;
280}
281
282static int tcf_ctinfo_dump(struct sk_buff *skb, struct tc_action *a,
283			   int bind, int ref)
284{
285	const struct tcf_ctinfo *ci = to_ctinfo(a);
286	unsigned char *b = skb_tail_pointer(skb);
287	const struct tcf_ctinfo_params *cp;
288	struct tc_ctinfo opt = {
289		.index   = ci->tcf_index,
290		.refcnt  = refcount_read(&ci->tcf_refcnt) - ref,
291		.bindcnt = atomic_read(&ci->tcf_bindcnt) - bind,
292	};
293	struct tcf_t t;
294
295	rcu_read_lock();
296	cp = rcu_dereference(ci->params);
297
298	tcf_tm_dump(&t, &ci->tcf_tm);
299	if (nla_put_64bit(skb, TCA_CTINFO_TM, sizeof(t), &t, TCA_CTINFO_PAD))
300		goto nla_put_failure;
301
302	opt.action = cp->action;
303	if (nla_put(skb, TCA_CTINFO_ACT, sizeof(opt), &opt))
304		goto nla_put_failure;
305
306	if (nla_put_u16(skb, TCA_CTINFO_ZONE, cp->zone))
307		goto nla_put_failure;
308
309	if (cp->mode & CTINFO_MODE_DSCP) {
310		if (nla_put_u32(skb, TCA_CTINFO_PARMS_DSCP_MASK,
311				cp->dscpmask))
312			goto nla_put_failure;
313		if (nla_put_u32(skb, TCA_CTINFO_PARMS_DSCP_STATEMASK,
314				cp->dscpstatemask))
315			goto nla_put_failure;
316	}
317
318	if (cp->mode & CTINFO_MODE_CPMARK) {
319		if (nla_put_u32(skb, TCA_CTINFO_PARMS_CPMARK_MASK,
320				cp->cpmarkmask))
321			goto nla_put_failure;
322	}
323
324	if (nla_put_u64_64bit(skb, TCA_CTINFO_STATS_DSCP_SET,
325			      atomic64_read(&ci->stats_dscp_set),
326			      TCA_CTINFO_PAD))
327		goto nla_put_failure;
328
329	if (nla_put_u64_64bit(skb, TCA_CTINFO_STATS_DSCP_ERROR,
330			      atomic64_read(&ci->stats_dscp_error),
331			      TCA_CTINFO_PAD))
332		goto nla_put_failure;
333
334	if (nla_put_u64_64bit(skb, TCA_CTINFO_STATS_CPMARK_SET,
335			      atomic64_read(&ci->stats_cpmark_set),
336			      TCA_CTINFO_PAD))
337		goto nla_put_failure;
338
339	rcu_read_unlock();
340	return skb->len;
341
342nla_put_failure:
343	rcu_read_unlock();
344	nlmsg_trim(skb, b);
345	return -1;
346}
347
348static void tcf_ctinfo_cleanup(struct tc_action *a)
349{
350	struct tcf_ctinfo *ci = to_ctinfo(a);
351	struct tcf_ctinfo_params *cp;
352
353	cp = rcu_dereference_protected(ci->params, 1);
354	if (cp)
355		kfree_rcu(cp, rcu);
356}
357
358static struct tc_action_ops act_ctinfo_ops = {
359	.kind	= "ctinfo",
360	.id	= TCA_ID_CTINFO,
361	.owner	= THIS_MODULE,
362	.act	= tcf_ctinfo_act,
363	.dump	= tcf_ctinfo_dump,
364	.init	= tcf_ctinfo_init,
365	.cleanup= tcf_ctinfo_cleanup,
366	.size	= sizeof(struct tcf_ctinfo),
367};
368MODULE_ALIAS_NET_ACT("ctinfo");
369
370static __net_init int ctinfo_init_net(struct net *net)
371{
372	struct tc_action_net *tn = net_generic(net, act_ctinfo_ops.net_id);
373
374	return tc_action_net_init(net, tn, &act_ctinfo_ops);
375}
376
377static void __net_exit ctinfo_exit_net(struct list_head *net_list)
378{
379	tc_action_net_exit(net_list, act_ctinfo_ops.net_id);
380}
381
382static struct pernet_operations ctinfo_net_ops = {
383	.init		= ctinfo_init_net,
384	.exit_batch	= ctinfo_exit_net,
385	.id		= &act_ctinfo_ops.net_id,
386	.size		= sizeof(struct tc_action_net),
387};
388
389static int __init ctinfo_init_module(void)
390{
391	return tcf_register_action(&act_ctinfo_ops, &ctinfo_net_ops);
392}
393
394static void __exit ctinfo_cleanup_module(void)
395{
396	tcf_unregister_action(&act_ctinfo_ops, &ctinfo_net_ops);
397}
398
399module_init(ctinfo_init_module);
400module_exit(ctinfo_cleanup_module);
401MODULE_AUTHOR("Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>");
402MODULE_DESCRIPTION("Connection tracking mark actions");
403MODULE_LICENSE("GPL");
~/ Read-only file preview.