xref: /kernel/linux/linux-5.10/net/sched/em_ipt.c (revision 8c2ecf20)
18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * net/sched/em_ipt.c IPtables matches Ematch
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (c) 2018 Eyal Birger <eyal.birger@gmail.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/gfp.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/types.h>
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/string.h>
138c2ecf20Sopenharmony_ci#include <linux/skbuff.h>
148c2ecf20Sopenharmony_ci#include <linux/tc_ematch/tc_em_ipt.h>
158c2ecf20Sopenharmony_ci#include <linux/netfilter.h>
168c2ecf20Sopenharmony_ci#include <linux/netfilter/x_tables.h>
178c2ecf20Sopenharmony_ci#include <linux/netfilter_ipv4/ip_tables.h>
188c2ecf20Sopenharmony_ci#include <linux/netfilter_ipv6/ip6_tables.h>
198c2ecf20Sopenharmony_ci#include <net/pkt_cls.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct em_ipt_match {
228c2ecf20Sopenharmony_ci	const struct xt_match *match;
238c2ecf20Sopenharmony_ci	u32 hook;
248c2ecf20Sopenharmony_ci	u8 nfproto;
258c2ecf20Sopenharmony_ci	u8 match_data[] __aligned(8);
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistruct em_ipt_xt_match {
298c2ecf20Sopenharmony_ci	char *match_name;
308c2ecf20Sopenharmony_ci	int (*validate_match_data)(struct nlattr **tb, u8 mrev);
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic const struct nla_policy em_ipt_policy[TCA_EM_IPT_MAX + 1] = {
348c2ecf20Sopenharmony_ci	[TCA_EM_IPT_MATCH_NAME]		= { .type = NLA_STRING,
358c2ecf20Sopenharmony_ci					    .len = XT_EXTENSION_MAXNAMELEN },
368c2ecf20Sopenharmony_ci	[TCA_EM_IPT_MATCH_REVISION]	= { .type = NLA_U8 },
378c2ecf20Sopenharmony_ci	[TCA_EM_IPT_HOOK]		= { .type = NLA_U32 },
388c2ecf20Sopenharmony_ci	[TCA_EM_IPT_NFPROTO]		= { .type = NLA_U8 },
398c2ecf20Sopenharmony_ci	[TCA_EM_IPT_MATCH_DATA]		= { .type = NLA_UNSPEC },
408c2ecf20Sopenharmony_ci};
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic int check_match(struct net *net, struct em_ipt_match *im, int mdata_len)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	struct xt_mtchk_param mtpar = {};
458c2ecf20Sopenharmony_ci	union {
468c2ecf20Sopenharmony_ci		struct ipt_entry e4;
478c2ecf20Sopenharmony_ci		struct ip6t_entry e6;
488c2ecf20Sopenharmony_ci	} e = {};
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci	mtpar.net	= net;
518c2ecf20Sopenharmony_ci	mtpar.table	= "filter";
528c2ecf20Sopenharmony_ci	mtpar.hook_mask	= 1 << im->hook;
538c2ecf20Sopenharmony_ci	mtpar.family	= im->match->family;
548c2ecf20Sopenharmony_ci	mtpar.match	= im->match;
558c2ecf20Sopenharmony_ci	mtpar.entryinfo = &e;
568c2ecf20Sopenharmony_ci	mtpar.matchinfo	= (void *)im->match_data;
578c2ecf20Sopenharmony_ci	return xt_check_match(&mtpar, mdata_len, 0, 0);
588c2ecf20Sopenharmony_ci}
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic int policy_validate_match_data(struct nlattr **tb, u8 mrev)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	if (mrev != 0) {
638c2ecf20Sopenharmony_ci		pr_err("only policy match revision 0 supported");
648c2ecf20Sopenharmony_ci		return -EINVAL;
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) != NF_INET_PRE_ROUTING) {
688c2ecf20Sopenharmony_ci		pr_err("policy can only be matched on NF_INET_PRE_ROUTING");
698c2ecf20Sopenharmony_ci		return -EINVAL;
708c2ecf20Sopenharmony_ci	}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	return 0;
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic int addrtype_validate_match_data(struct nlattr **tb, u8 mrev)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	if (mrev != 1) {
788c2ecf20Sopenharmony_ci		pr_err("only addrtype match revision 1 supported");
798c2ecf20Sopenharmony_ci		return -EINVAL;
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	return 0;
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic const struct em_ipt_xt_match em_ipt_xt_matches[] = {
868c2ecf20Sopenharmony_ci	{
878c2ecf20Sopenharmony_ci		.match_name = "policy",
888c2ecf20Sopenharmony_ci		.validate_match_data = policy_validate_match_data
898c2ecf20Sopenharmony_ci	},
908c2ecf20Sopenharmony_ci	{
918c2ecf20Sopenharmony_ci		.match_name = "addrtype",
928c2ecf20Sopenharmony_ci		.validate_match_data = addrtype_validate_match_data
938c2ecf20Sopenharmony_ci	},
948c2ecf20Sopenharmony_ci	{}
958c2ecf20Sopenharmony_ci};
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic struct xt_match *get_xt_match(struct nlattr **tb)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	const struct em_ipt_xt_match *m;
1008c2ecf20Sopenharmony_ci	struct nlattr *mname_attr;
1018c2ecf20Sopenharmony_ci	u8 nfproto, mrev = 0;
1028c2ecf20Sopenharmony_ci	int ret;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	mname_attr = tb[TCA_EM_IPT_MATCH_NAME];
1058c2ecf20Sopenharmony_ci	for (m = em_ipt_xt_matches; m->match_name; m++) {
1068c2ecf20Sopenharmony_ci		if (!nla_strcmp(mname_attr, m->match_name))
1078c2ecf20Sopenharmony_ci			break;
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (!m->match_name) {
1118c2ecf20Sopenharmony_ci		pr_err("Unsupported xt match");
1128c2ecf20Sopenharmony_ci		return ERR_PTR(-EINVAL);
1138c2ecf20Sopenharmony_ci	}
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	if (tb[TCA_EM_IPT_MATCH_REVISION])
1168c2ecf20Sopenharmony_ci		mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]);
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	ret = m->validate_match_data(tb, mrev);
1198c2ecf20Sopenharmony_ci	if (ret < 0)
1208c2ecf20Sopenharmony_ci		return ERR_PTR(ret);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]);
1238c2ecf20Sopenharmony_ci	return xt_request_find_match(nfproto, m->match_name, mrev);
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int em_ipt_change(struct net *net, void *data, int data_len,
1278c2ecf20Sopenharmony_ci			 struct tcf_ematch *em)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	struct nlattr *tb[TCA_EM_IPT_MAX + 1];
1308c2ecf20Sopenharmony_ci	struct em_ipt_match *im = NULL;
1318c2ecf20Sopenharmony_ci	struct xt_match *match;
1328c2ecf20Sopenharmony_ci	int mdata_len, ret;
1338c2ecf20Sopenharmony_ci	u8 nfproto;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len,
1368c2ecf20Sopenharmony_ci				   em_ipt_policy, NULL);
1378c2ecf20Sopenharmony_ci	if (ret < 0)
1388c2ecf20Sopenharmony_ci		return ret;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] ||
1418c2ecf20Sopenharmony_ci	    !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO])
1428c2ecf20Sopenharmony_ci		return -EINVAL;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]);
1458c2ecf20Sopenharmony_ci	switch (nfproto) {
1468c2ecf20Sopenharmony_ci	case NFPROTO_IPV4:
1478c2ecf20Sopenharmony_ci	case NFPROTO_IPV6:
1488c2ecf20Sopenharmony_ci		break;
1498c2ecf20Sopenharmony_ci	default:
1508c2ecf20Sopenharmony_ci		return -EINVAL;
1518c2ecf20Sopenharmony_ci	}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	match = get_xt_match(tb);
1548c2ecf20Sopenharmony_ci	if (IS_ERR(match)) {
1558c2ecf20Sopenharmony_ci		pr_err("unable to load match\n");
1568c2ecf20Sopenharmony_ci		return PTR_ERR(match);
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA]));
1608c2ecf20Sopenharmony_ci	im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL);
1618c2ecf20Sopenharmony_ci	if (!im) {
1628c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1638c2ecf20Sopenharmony_ci		goto err;
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	im->match = match;
1678c2ecf20Sopenharmony_ci	im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]);
1688c2ecf20Sopenharmony_ci	im->nfproto = nfproto;
1698c2ecf20Sopenharmony_ci	nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len);
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	ret = check_match(net, im, mdata_len);
1728c2ecf20Sopenharmony_ci	if (ret)
1738c2ecf20Sopenharmony_ci		goto err;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	em->datalen = sizeof(*im) + mdata_len;
1768c2ecf20Sopenharmony_ci	em->data = (unsigned long)im;
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cierr:
1808c2ecf20Sopenharmony_ci	kfree(im);
1818c2ecf20Sopenharmony_ci	module_put(match->me);
1828c2ecf20Sopenharmony_ci	return ret;
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic void em_ipt_destroy(struct tcf_ematch *em)
1868c2ecf20Sopenharmony_ci{
1878c2ecf20Sopenharmony_ci	struct em_ipt_match *im = (void *)em->data;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (!im)
1908c2ecf20Sopenharmony_ci		return;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	if (im->match->destroy) {
1938c2ecf20Sopenharmony_ci		struct xt_mtdtor_param par = {
1948c2ecf20Sopenharmony_ci			.net = em->net,
1958c2ecf20Sopenharmony_ci			.match = im->match,
1968c2ecf20Sopenharmony_ci			.matchinfo = im->match_data,
1978c2ecf20Sopenharmony_ci			.family = im->match->family
1988c2ecf20Sopenharmony_ci		};
1998c2ecf20Sopenharmony_ci		im->match->destroy(&par);
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci	module_put(im->match->me);
2028c2ecf20Sopenharmony_ci	kfree(im);
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em,
2068c2ecf20Sopenharmony_ci			struct tcf_pkt_info *info)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	const struct em_ipt_match *im = (const void *)em->data;
2098c2ecf20Sopenharmony_ci	struct xt_action_param acpar = {};
2108c2ecf20Sopenharmony_ci	struct net_device *indev = NULL;
2118c2ecf20Sopenharmony_ci	u8 nfproto = im->match->family;
2128c2ecf20Sopenharmony_ci	struct nf_hook_state state;
2138c2ecf20Sopenharmony_ci	int ret;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	switch (skb_protocol(skb, true)) {
2168c2ecf20Sopenharmony_ci	case htons(ETH_P_IP):
2178c2ecf20Sopenharmony_ci		if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
2188c2ecf20Sopenharmony_ci			return 0;
2198c2ecf20Sopenharmony_ci		if (nfproto == NFPROTO_UNSPEC)
2208c2ecf20Sopenharmony_ci			nfproto = NFPROTO_IPV4;
2218c2ecf20Sopenharmony_ci		break;
2228c2ecf20Sopenharmony_ci	case htons(ETH_P_IPV6):
2238c2ecf20Sopenharmony_ci		if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
2248c2ecf20Sopenharmony_ci			return 0;
2258c2ecf20Sopenharmony_ci		if (nfproto == NFPROTO_UNSPEC)
2268c2ecf20Sopenharmony_ci			nfproto = NFPROTO_IPV6;
2278c2ecf20Sopenharmony_ci		break;
2288c2ecf20Sopenharmony_ci	default:
2298c2ecf20Sopenharmony_ci		return 0;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	rcu_read_lock();
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	if (skb->skb_iif)
2358c2ecf20Sopenharmony_ci		indev = dev_get_by_index_rcu(em->net, skb->skb_iif);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	nf_hook_state_init(&state, im->hook, nfproto,
2388c2ecf20Sopenharmony_ci			   indev ?: skb->dev, skb->dev, NULL, em->net, NULL);
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	acpar.match = im->match;
2418c2ecf20Sopenharmony_ci	acpar.matchinfo = im->match_data;
2428c2ecf20Sopenharmony_ci	acpar.state = &state;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	ret = im->match->match(skb, &acpar);
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	rcu_read_unlock();
2478c2ecf20Sopenharmony_ci	return ret;
2488c2ecf20Sopenharmony_ci}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_cistatic int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em)
2518c2ecf20Sopenharmony_ci{
2528c2ecf20Sopenharmony_ci	struct em_ipt_match *im = (void *)em->data;
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ci	if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0)
2558c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2568c2ecf20Sopenharmony_ci	if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0)
2578c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2588c2ecf20Sopenharmony_ci	if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0)
2598c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2608c2ecf20Sopenharmony_ci	if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0)
2618c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2628c2ecf20Sopenharmony_ci	if (nla_put(skb, TCA_EM_IPT_MATCH_DATA,
2638c2ecf20Sopenharmony_ci		    im->match->usersize ?: im->match->matchsize,
2648c2ecf20Sopenharmony_ci		    im->match_data) < 0)
2658c2ecf20Sopenharmony_ci		return -EMSGSIZE;
2668c2ecf20Sopenharmony_ci
2678c2ecf20Sopenharmony_ci	return 0;
2688c2ecf20Sopenharmony_ci}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cistatic struct tcf_ematch_ops em_ipt_ops = {
2718c2ecf20Sopenharmony_ci	.kind	  = TCF_EM_IPT,
2728c2ecf20Sopenharmony_ci	.change	  = em_ipt_change,
2738c2ecf20Sopenharmony_ci	.destroy  = em_ipt_destroy,
2748c2ecf20Sopenharmony_ci	.match	  = em_ipt_match,
2758c2ecf20Sopenharmony_ci	.dump	  = em_ipt_dump,
2768c2ecf20Sopenharmony_ci	.owner	  = THIS_MODULE,
2778c2ecf20Sopenharmony_ci	.link	  = LIST_HEAD_INIT(em_ipt_ops.link)
2788c2ecf20Sopenharmony_ci};
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cistatic int __init init_em_ipt(void)
2818c2ecf20Sopenharmony_ci{
2828c2ecf20Sopenharmony_ci	return tcf_em_register(&em_ipt_ops);
2838c2ecf20Sopenharmony_ci}
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cistatic void __exit exit_em_ipt(void)
2868c2ecf20Sopenharmony_ci{
2878c2ecf20Sopenharmony_ci	tcf_em_unregister(&em_ipt_ops);
2888c2ecf20Sopenharmony_ci}
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2918c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>");
2928c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TC extended match for IPtables matches");
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cimodule_init(init_em_ipt);
2958c2ecf20Sopenharmony_cimodule_exit(exit_em_ipt);
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_ciMODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT);
298