18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * net/sched/act_connmark.c  netfilter connmark retriever action
48c2ecf20Sopenharmony_ci * skb mark is over-written
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (c) 2011 Felix Fietkau <nbd@openwrt.org>
78c2ecf20Sopenharmony_ci*/
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
138c2ecf20Sopenharmony_ci#include <linux/rtnetlink.h>
148c2ecf20Sopenharmony_ci#include <linux/pkt_cls.h>
158c2ecf20Sopenharmony_ci#include <linux/ip.h>
168c2ecf20Sopenharmony_ci#include <linux/ipv6.h>
178c2ecf20Sopenharmony_ci#include <net/netlink.h>
188c2ecf20Sopenharmony_ci#include <net/pkt_sched.h>
198c2ecf20Sopenharmony_ci#include <net/act_api.h>
208c2ecf20Sopenharmony_ci#include <net/pkt_cls.h>
218c2ecf20Sopenharmony_ci#include <uapi/linux/tc_act/tc_connmark.h>
228c2ecf20Sopenharmony_ci#include <net/tc_act/tc_connmark.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#include <net/netfilter/nf_conntrack.h>
258c2ecf20Sopenharmony_ci#include <net/netfilter/nf_conntrack_core.h>
268c2ecf20Sopenharmony_ci#include <net/netfilter/nf_conntrack_zones.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic unsigned int connmark_net_id;
298c2ecf20Sopenharmony_cistatic struct tc_action_ops act_connmark_ops;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int tcf_connmark_act(struct sk_buff *skb, const struct tc_action *a,
328c2ecf20Sopenharmony_ci			    struct tcf_result *res)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	const struct nf_conntrack_tuple_hash *thash;
358c2ecf20Sopenharmony_ci	struct nf_conntrack_tuple tuple;
368c2ecf20Sopenharmony_ci	enum ip_conntrack_info ctinfo;
378c2ecf20Sopenharmony_ci	struct tcf_connmark_info *ca = to_connmark(a);
388c2ecf20Sopenharmony_ci	struct nf_conntrack_zone zone;
398c2ecf20Sopenharmony_ci	struct nf_conn *c;
408c2ecf20Sopenharmony_ci	int proto;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	spin_lock(&ca->tcf_lock);
438c2ecf20Sopenharmony_ci	tcf_lastuse_update(&ca->tcf_tm);
448c2ecf20Sopenharmony_ci	bstats_update(&ca->tcf_bstats, skb);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	switch (skb_protocol(skb, true)) {
478c2ecf20Sopenharmony_ci	case htons(ETH_P_IP):
488c2ecf20Sopenharmony_ci		if (skb->len < sizeof(struct iphdr))
498c2ecf20Sopenharmony_ci			goto out;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci		proto = NFPROTO_IPV4;
528c2ecf20Sopenharmony_ci		break;
538c2ecf20Sopenharmony_ci	case htons(ETH_P_IPV6):
548c2ecf20Sopenharmony_ci		if (skb->len < sizeof(struct ipv6hdr))
558c2ecf20Sopenharmony_ci			goto out;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci		proto = NFPROTO_IPV6;
588c2ecf20Sopenharmony_ci		break;
598c2ecf20Sopenharmony_ci	default:
608c2ecf20Sopenharmony_ci		goto out;
618c2ecf20Sopenharmony_ci	}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	c = nf_ct_get(skb, &ctinfo);
648c2ecf20Sopenharmony_ci	if (c) {
658c2ecf20Sopenharmony_ci		skb->mark = READ_ONCE(c->mark);
668c2ecf20Sopenharmony_ci		/* using overlimits stats to count how many packets marked */
678c2ecf20Sopenharmony_ci		ca->tcf_qstats.overlimits++;
688c2ecf20Sopenharmony_ci		goto out;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	if (!nf_ct_get_tuplepr(skb, skb_network_offset(skb),
728c2ecf20Sopenharmony_ci			       proto, ca->net, &tuple))
738c2ecf20Sopenharmony_ci		goto out;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	zone.id = ca->zone;
768c2ecf20Sopenharmony_ci	zone.dir = NF_CT_DEFAULT_ZONE_DIR;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	thash = nf_conntrack_find_get(ca->net, &zone, &tuple);
798c2ecf20Sopenharmony_ci	if (!thash)
808c2ecf20Sopenharmony_ci		goto out;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	c = nf_ct_tuplehash_to_ctrack(thash);
838c2ecf20Sopenharmony_ci	/* using overlimits stats to count how many packets marked */
848c2ecf20Sopenharmony_ci	ca->tcf_qstats.overlimits++;
858c2ecf20Sopenharmony_ci	skb->mark = READ_ONCE(c->mark);
868c2ecf20Sopenharmony_ci	nf_ct_put(c);
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ciout:
898c2ecf20Sopenharmony_ci	spin_unlock(&ca->tcf_lock);
908c2ecf20Sopenharmony_ci	return ca->tcf_action;
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic const struct nla_policy connmark_policy[TCA_CONNMARK_MAX + 1] = {
948c2ecf20Sopenharmony_ci	[TCA_CONNMARK_PARMS] = { .len = sizeof(struct tc_connmark) },
958c2ecf20Sopenharmony_ci};
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic int tcf_connmark_init(struct net *net, struct nlattr *nla,
988c2ecf20Sopenharmony_ci			     struct nlattr *est, struct tc_action **a,
998c2ecf20Sopenharmony_ci			     int ovr, int bind, bool rtnl_held,
1008c2ecf20Sopenharmony_ci			     struct tcf_proto *tp, u32 flags,
1018c2ecf20Sopenharmony_ci			     struct netlink_ext_ack *extack)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, connmark_net_id);
1048c2ecf20Sopenharmony_ci	struct nlattr *tb[TCA_CONNMARK_MAX + 1];
1058c2ecf20Sopenharmony_ci	struct tcf_chain *goto_ch = NULL;
1068c2ecf20Sopenharmony_ci	struct tcf_connmark_info *ci;
1078c2ecf20Sopenharmony_ci	struct tc_connmark *parm;
1088c2ecf20Sopenharmony_ci	int ret = 0, err;
1098c2ecf20Sopenharmony_ci	u32 index;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (!nla)
1128c2ecf20Sopenharmony_ci		return -EINVAL;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	ret = nla_parse_nested_deprecated(tb, TCA_CONNMARK_MAX, nla,
1158c2ecf20Sopenharmony_ci					  connmark_policy, NULL);
1168c2ecf20Sopenharmony_ci	if (ret < 0)
1178c2ecf20Sopenharmony_ci		return ret;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (!tb[TCA_CONNMARK_PARMS])
1208c2ecf20Sopenharmony_ci		return -EINVAL;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	parm = nla_data(tb[TCA_CONNMARK_PARMS]);
1238c2ecf20Sopenharmony_ci	index = parm->index;
1248c2ecf20Sopenharmony_ci	ret = tcf_idr_check_alloc(tn, &index, a, bind);
1258c2ecf20Sopenharmony_ci	if (!ret) {
1268c2ecf20Sopenharmony_ci		ret = tcf_idr_create(tn, index, est, a,
1278c2ecf20Sopenharmony_ci				     &act_connmark_ops, bind, false, flags);
1288c2ecf20Sopenharmony_ci		if (ret) {
1298c2ecf20Sopenharmony_ci			tcf_idr_cleanup(tn, index);
1308c2ecf20Sopenharmony_ci			return ret;
1318c2ecf20Sopenharmony_ci		}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		ci = to_connmark(*a);
1348c2ecf20Sopenharmony_ci		err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch,
1358c2ecf20Sopenharmony_ci					       extack);
1368c2ecf20Sopenharmony_ci		if (err < 0)
1378c2ecf20Sopenharmony_ci			goto release_idr;
1388c2ecf20Sopenharmony_ci		tcf_action_set_ctrlact(*a, parm->action, goto_ch);
1398c2ecf20Sopenharmony_ci		ci->net = net;
1408c2ecf20Sopenharmony_ci		ci->zone = parm->zone;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		ret = ACT_P_CREATED;
1438c2ecf20Sopenharmony_ci	} else if (ret > 0) {
1448c2ecf20Sopenharmony_ci		ci = to_connmark(*a);
1458c2ecf20Sopenharmony_ci		if (bind)
1468c2ecf20Sopenharmony_ci			return 0;
1478c2ecf20Sopenharmony_ci		if (!ovr) {
1488c2ecf20Sopenharmony_ci			tcf_idr_release(*a, bind);
1498c2ecf20Sopenharmony_ci			return -EEXIST;
1508c2ecf20Sopenharmony_ci		}
1518c2ecf20Sopenharmony_ci		err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch,
1528c2ecf20Sopenharmony_ci					       extack);
1538c2ecf20Sopenharmony_ci		if (err < 0)
1548c2ecf20Sopenharmony_ci			goto release_idr;
1558c2ecf20Sopenharmony_ci		/* replacing action and zone */
1568c2ecf20Sopenharmony_ci		spin_lock_bh(&ci->tcf_lock);
1578c2ecf20Sopenharmony_ci		goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
1588c2ecf20Sopenharmony_ci		ci->zone = parm->zone;
1598c2ecf20Sopenharmony_ci		spin_unlock_bh(&ci->tcf_lock);
1608c2ecf20Sopenharmony_ci		if (goto_ch)
1618c2ecf20Sopenharmony_ci			tcf_chain_put_by_act(goto_ch);
1628c2ecf20Sopenharmony_ci		ret = 0;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	return ret;
1668c2ecf20Sopenharmony_cirelease_idr:
1678c2ecf20Sopenharmony_ci	tcf_idr_release(*a, bind);
1688c2ecf20Sopenharmony_ci	return err;
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic inline int tcf_connmark_dump(struct sk_buff *skb, struct tc_action *a,
1728c2ecf20Sopenharmony_ci				    int bind, int ref)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	unsigned char *b = skb_tail_pointer(skb);
1758c2ecf20Sopenharmony_ci	struct tcf_connmark_info *ci = to_connmark(a);
1768c2ecf20Sopenharmony_ci	struct tc_connmark opt = {
1778c2ecf20Sopenharmony_ci		.index   = ci->tcf_index,
1788c2ecf20Sopenharmony_ci		.refcnt  = refcount_read(&ci->tcf_refcnt) - ref,
1798c2ecf20Sopenharmony_ci		.bindcnt = atomic_read(&ci->tcf_bindcnt) - bind,
1808c2ecf20Sopenharmony_ci	};
1818c2ecf20Sopenharmony_ci	struct tcf_t t;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	spin_lock_bh(&ci->tcf_lock);
1848c2ecf20Sopenharmony_ci	opt.action = ci->tcf_action;
1858c2ecf20Sopenharmony_ci	opt.zone = ci->zone;
1868c2ecf20Sopenharmony_ci	if (nla_put(skb, TCA_CONNMARK_PARMS, sizeof(opt), &opt))
1878c2ecf20Sopenharmony_ci		goto nla_put_failure;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	tcf_tm_dump(&t, &ci->tcf_tm);
1908c2ecf20Sopenharmony_ci	if (nla_put_64bit(skb, TCA_CONNMARK_TM, sizeof(t), &t,
1918c2ecf20Sopenharmony_ci			  TCA_CONNMARK_PAD))
1928c2ecf20Sopenharmony_ci		goto nla_put_failure;
1938c2ecf20Sopenharmony_ci	spin_unlock_bh(&ci->tcf_lock);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	return skb->len;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cinla_put_failure:
1988c2ecf20Sopenharmony_ci	spin_unlock_bh(&ci->tcf_lock);
1998c2ecf20Sopenharmony_ci	nlmsg_trim(skb, b);
2008c2ecf20Sopenharmony_ci	return -1;
2018c2ecf20Sopenharmony_ci}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic int tcf_connmark_walker(struct net *net, struct sk_buff *skb,
2048c2ecf20Sopenharmony_ci			       struct netlink_callback *cb, int type,
2058c2ecf20Sopenharmony_ci			       const struct tc_action_ops *ops,
2068c2ecf20Sopenharmony_ci			       struct netlink_ext_ack *extack)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, connmark_net_id);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	return tcf_generic_walker(tn, skb, cb, type, ops, extack);
2118c2ecf20Sopenharmony_ci}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_cistatic int tcf_connmark_search(struct net *net, struct tc_action **a, u32 index)
2148c2ecf20Sopenharmony_ci{
2158c2ecf20Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, connmark_net_id);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	return tcf_idr_search(tn, a, index);
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_cistatic struct tc_action_ops act_connmark_ops = {
2218c2ecf20Sopenharmony_ci	.kind		=	"connmark",
2228c2ecf20Sopenharmony_ci	.id		=	TCA_ID_CONNMARK,
2238c2ecf20Sopenharmony_ci	.owner		=	THIS_MODULE,
2248c2ecf20Sopenharmony_ci	.act		=	tcf_connmark_act,
2258c2ecf20Sopenharmony_ci	.dump		=	tcf_connmark_dump,
2268c2ecf20Sopenharmony_ci	.init		=	tcf_connmark_init,
2278c2ecf20Sopenharmony_ci	.walk		=	tcf_connmark_walker,
2288c2ecf20Sopenharmony_ci	.lookup		=	tcf_connmark_search,
2298c2ecf20Sopenharmony_ci	.size		=	sizeof(struct tcf_connmark_info),
2308c2ecf20Sopenharmony_ci};
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic __net_init int connmark_init_net(struct net *net)
2338c2ecf20Sopenharmony_ci{
2348c2ecf20Sopenharmony_ci	struct tc_action_net *tn = net_generic(net, connmark_net_id);
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	return tc_action_net_init(net, tn, &act_connmark_ops);
2378c2ecf20Sopenharmony_ci}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic void __net_exit connmark_exit_net(struct list_head *net_list)
2408c2ecf20Sopenharmony_ci{
2418c2ecf20Sopenharmony_ci	tc_action_net_exit(net_list, connmark_net_id);
2428c2ecf20Sopenharmony_ci}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic struct pernet_operations connmark_net_ops = {
2458c2ecf20Sopenharmony_ci	.init = connmark_init_net,
2468c2ecf20Sopenharmony_ci	.exit_batch = connmark_exit_net,
2478c2ecf20Sopenharmony_ci	.id   = &connmark_net_id,
2488c2ecf20Sopenharmony_ci	.size = sizeof(struct tc_action_net),
2498c2ecf20Sopenharmony_ci};
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_cistatic int __init connmark_init_module(void)
2528c2ecf20Sopenharmony_ci{
2538c2ecf20Sopenharmony_ci	return tcf_register_action(&act_connmark_ops, &connmark_net_ops);
2548c2ecf20Sopenharmony_ci}
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_cistatic void __exit connmark_cleanup_module(void)
2578c2ecf20Sopenharmony_ci{
2588c2ecf20Sopenharmony_ci	tcf_unregister_action(&act_connmark_ops, &connmark_net_ops);
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cimodule_init(connmark_init_module);
2628c2ecf20Sopenharmony_cimodule_exit(connmark_cleanup_module);
2638c2ecf20Sopenharmony_ciMODULE_AUTHOR("Felix Fietkau <nbd@openwrt.org>");
2648c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Connection tracking mark restoring");
2658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
266