162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * net/sched/em_ipt.c IPtables matches Ematch
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * (c) 2018 Eyal Birger <eyal.birger@gmail.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/gfp.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/types.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/string.h>
1362306a36Sopenharmony_ci#include <linux/skbuff.h>
1462306a36Sopenharmony_ci#include <linux/tc_ematch/tc_em_ipt.h>
1562306a36Sopenharmony_ci#include <linux/netfilter.h>
1662306a36Sopenharmony_ci#include <linux/netfilter/x_tables.h>
1762306a36Sopenharmony_ci#include <linux/netfilter_ipv4/ip_tables.h>
1862306a36Sopenharmony_ci#include <linux/netfilter_ipv6/ip6_tables.h>
1962306a36Sopenharmony_ci#include <net/pkt_cls.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistruct em_ipt_match {
2262306a36Sopenharmony_ci	const struct xt_match *match;
2362306a36Sopenharmony_ci	u32 hook;
2462306a36Sopenharmony_ci	u8 nfproto;
2562306a36Sopenharmony_ci	u8 match_data[] __aligned(8);
2662306a36Sopenharmony_ci};
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct em_ipt_xt_match {
2962306a36Sopenharmony_ci	char *match_name;
3062306a36Sopenharmony_ci	int (*validate_match_data)(struct nlattr **tb, u8 mrev);
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic const struct nla_policy em_ipt_policy[TCA_EM_IPT_MAX + 1] = {
3462306a36Sopenharmony_ci	[TCA_EM_IPT_MATCH_NAME]		= { .type = NLA_STRING,
3562306a36Sopenharmony_ci					    .len = XT_EXTENSION_MAXNAMELEN },
3662306a36Sopenharmony_ci	[TCA_EM_IPT_MATCH_REVISION]	= { .type = NLA_U8 },
3762306a36Sopenharmony_ci	[TCA_EM_IPT_HOOK]		= { .type = NLA_U32 },
3862306a36Sopenharmony_ci	[TCA_EM_IPT_NFPROTO]		= { .type = NLA_U8 },
3962306a36Sopenharmony_ci	[TCA_EM_IPT_MATCH_DATA]		= { .type = NLA_UNSPEC },
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int check_match(struct net *net, struct em_ipt_match *im, int mdata_len)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct xt_mtchk_param mtpar = {};
4562306a36Sopenharmony_ci	union {
4662306a36Sopenharmony_ci		struct ipt_entry e4;
4762306a36Sopenharmony_ci		struct ip6t_entry e6;
4862306a36Sopenharmony_ci	} e = {};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	mtpar.net	= net;
5162306a36Sopenharmony_ci	mtpar.table	= "filter";
5262306a36Sopenharmony_ci	mtpar.hook_mask	= 1 << im->hook;
5362306a36Sopenharmony_ci	mtpar.family	= im->match->family;
5462306a36Sopenharmony_ci	mtpar.match	= im->match;
5562306a36Sopenharmony_ci	mtpar.entryinfo = &e;
5662306a36Sopenharmony_ci	mtpar.matchinfo	= (void *)im->match_data;
5762306a36Sopenharmony_ci	return xt_check_match(&mtpar, mdata_len, 0, 0);
5862306a36Sopenharmony_ci}
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_cistatic int policy_validate_match_data(struct nlattr **tb, u8 mrev)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	if (mrev != 0) {
6362306a36Sopenharmony_ci		pr_err("only policy match revision 0 supported");
6462306a36Sopenharmony_ci		return -EINVAL;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) != NF_INET_PRE_ROUTING) {
6862306a36Sopenharmony_ci		pr_err("policy can only be matched on NF_INET_PRE_ROUTING");
6962306a36Sopenharmony_ci		return -EINVAL;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	return 0;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic int addrtype_validate_match_data(struct nlattr **tb, u8 mrev)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	if (mrev != 1) {
7862306a36Sopenharmony_ci		pr_err("only addrtype match revision 1 supported");
7962306a36Sopenharmony_ci		return -EINVAL;
8062306a36Sopenharmony_ci	}
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	return 0;
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic const struct em_ipt_xt_match em_ipt_xt_matches[] = {
8662306a36Sopenharmony_ci	{
8762306a36Sopenharmony_ci		.match_name = "policy",
8862306a36Sopenharmony_ci		.validate_match_data = policy_validate_match_data
8962306a36Sopenharmony_ci	},
9062306a36Sopenharmony_ci	{
9162306a36Sopenharmony_ci		.match_name = "addrtype",
9262306a36Sopenharmony_ci		.validate_match_data = addrtype_validate_match_data
9362306a36Sopenharmony_ci	},
9462306a36Sopenharmony_ci	{}
9562306a36Sopenharmony_ci};
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic struct xt_match *get_xt_match(struct nlattr **tb)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	const struct em_ipt_xt_match *m;
10062306a36Sopenharmony_ci	struct nlattr *mname_attr;
10162306a36Sopenharmony_ci	u8 nfproto, mrev = 0;
10262306a36Sopenharmony_ci	int ret;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	mname_attr = tb[TCA_EM_IPT_MATCH_NAME];
10562306a36Sopenharmony_ci	for (m = em_ipt_xt_matches; m->match_name; m++) {
10662306a36Sopenharmony_ci		if (!nla_strcmp(mname_attr, m->match_name))
10762306a36Sopenharmony_ci			break;
10862306a36Sopenharmony_ci	}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (!m->match_name) {
11162306a36Sopenharmony_ci		pr_err("Unsupported xt match");
11262306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	if (tb[TCA_EM_IPT_MATCH_REVISION])
11662306a36Sopenharmony_ci		mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = m->validate_match_data(tb, mrev);
11962306a36Sopenharmony_ci	if (ret < 0)
12062306a36Sopenharmony_ci		return ERR_PTR(ret);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]);
12362306a36Sopenharmony_ci	return xt_request_find_match(nfproto, m->match_name, mrev);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int em_ipt_change(struct net *net, void *data, int data_len,
12762306a36Sopenharmony_ci			 struct tcf_ematch *em)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	struct nlattr *tb[TCA_EM_IPT_MAX + 1];
13062306a36Sopenharmony_ci	struct em_ipt_match *im = NULL;
13162306a36Sopenharmony_ci	struct xt_match *match;
13262306a36Sopenharmony_ci	int mdata_len, ret;
13362306a36Sopenharmony_ci	u8 nfproto;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len,
13662306a36Sopenharmony_ci				   em_ipt_policy, NULL);
13762306a36Sopenharmony_ci	if (ret < 0)
13862306a36Sopenharmony_ci		return ret;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] ||
14162306a36Sopenharmony_ci	    !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO])
14262306a36Sopenharmony_ci		return -EINVAL;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]);
14562306a36Sopenharmony_ci	switch (nfproto) {
14662306a36Sopenharmony_ci	case NFPROTO_IPV4:
14762306a36Sopenharmony_ci	case NFPROTO_IPV6:
14862306a36Sopenharmony_ci		break;
14962306a36Sopenharmony_ci	default:
15062306a36Sopenharmony_ci		return -EINVAL;
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	match = get_xt_match(tb);
15462306a36Sopenharmony_ci	if (IS_ERR(match)) {
15562306a36Sopenharmony_ci		pr_err("unable to load match\n");
15662306a36Sopenharmony_ci		return PTR_ERR(match);
15762306a36Sopenharmony_ci	}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA]));
16062306a36Sopenharmony_ci	im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL);
16162306a36Sopenharmony_ci	if (!im) {
16262306a36Sopenharmony_ci		ret = -ENOMEM;
16362306a36Sopenharmony_ci		goto err;
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	im->match = match;
16762306a36Sopenharmony_ci	im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]);
16862306a36Sopenharmony_ci	im->nfproto = nfproto;
16962306a36Sopenharmony_ci	nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	ret = check_match(net, im, mdata_len);
17262306a36Sopenharmony_ci	if (ret)
17362306a36Sopenharmony_ci		goto err;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	em->datalen = sizeof(*im) + mdata_len;
17662306a36Sopenharmony_ci	em->data = (unsigned long)im;
17762306a36Sopenharmony_ci	return 0;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cierr:
18062306a36Sopenharmony_ci	kfree(im);
18162306a36Sopenharmony_ci	module_put(match->me);
18262306a36Sopenharmony_ci	return ret;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic void em_ipt_destroy(struct tcf_ematch *em)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct em_ipt_match *im = (void *)em->data;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (!im)
19062306a36Sopenharmony_ci		return;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	if (im->match->destroy) {
19362306a36Sopenharmony_ci		struct xt_mtdtor_param par = {
19462306a36Sopenharmony_ci			.net = em->net,
19562306a36Sopenharmony_ci			.match = im->match,
19662306a36Sopenharmony_ci			.matchinfo = im->match_data,
19762306a36Sopenharmony_ci			.family = im->match->family
19862306a36Sopenharmony_ci		};
19962306a36Sopenharmony_ci		im->match->destroy(&par);
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ci	module_put(im->match->me);
20262306a36Sopenharmony_ci	kfree(im);
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em,
20662306a36Sopenharmony_ci			struct tcf_pkt_info *info)
20762306a36Sopenharmony_ci{
20862306a36Sopenharmony_ci	const struct em_ipt_match *im = (const void *)em->data;
20962306a36Sopenharmony_ci	struct xt_action_param acpar = {};
21062306a36Sopenharmony_ci	struct net_device *indev = NULL;
21162306a36Sopenharmony_ci	u8 nfproto = im->match->family;
21262306a36Sopenharmony_ci	struct nf_hook_state state;
21362306a36Sopenharmony_ci	int ret;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	switch (skb_protocol(skb, true)) {
21662306a36Sopenharmony_ci	case htons(ETH_P_IP):
21762306a36Sopenharmony_ci		if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
21862306a36Sopenharmony_ci			return 0;
21962306a36Sopenharmony_ci		if (nfproto == NFPROTO_UNSPEC)
22062306a36Sopenharmony_ci			nfproto = NFPROTO_IPV4;
22162306a36Sopenharmony_ci		break;
22262306a36Sopenharmony_ci	case htons(ETH_P_IPV6):
22362306a36Sopenharmony_ci		if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
22462306a36Sopenharmony_ci			return 0;
22562306a36Sopenharmony_ci		if (nfproto == NFPROTO_UNSPEC)
22662306a36Sopenharmony_ci			nfproto = NFPROTO_IPV6;
22762306a36Sopenharmony_ci		break;
22862306a36Sopenharmony_ci	default:
22962306a36Sopenharmony_ci		return 0;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	rcu_read_lock();
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (skb->skb_iif)
23562306a36Sopenharmony_ci		indev = dev_get_by_index_rcu(em->net, skb->skb_iif);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	nf_hook_state_init(&state, im->hook, nfproto,
23862306a36Sopenharmony_ci			   indev ?: skb->dev, skb->dev, NULL, em->net, NULL);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	acpar.match = im->match;
24162306a36Sopenharmony_ci	acpar.matchinfo = im->match_data;
24262306a36Sopenharmony_ci	acpar.state = &state;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	ret = im->match->match(skb, &acpar);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	rcu_read_unlock();
24762306a36Sopenharmony_ci	return ret;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct em_ipt_match *im = (void *)em->data;
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0)
25562306a36Sopenharmony_ci		return -EMSGSIZE;
25662306a36Sopenharmony_ci	if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0)
25762306a36Sopenharmony_ci		return -EMSGSIZE;
25862306a36Sopenharmony_ci	if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0)
25962306a36Sopenharmony_ci		return -EMSGSIZE;
26062306a36Sopenharmony_ci	if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0)
26162306a36Sopenharmony_ci		return -EMSGSIZE;
26262306a36Sopenharmony_ci	if (nla_put(skb, TCA_EM_IPT_MATCH_DATA,
26362306a36Sopenharmony_ci		    im->match->usersize ?: im->match->matchsize,
26462306a36Sopenharmony_ci		    im->match_data) < 0)
26562306a36Sopenharmony_ci		return -EMSGSIZE;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	return 0;
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic struct tcf_ematch_ops em_ipt_ops = {
27162306a36Sopenharmony_ci	.kind	  = TCF_EM_IPT,
27262306a36Sopenharmony_ci	.change	  = em_ipt_change,
27362306a36Sopenharmony_ci	.destroy  = em_ipt_destroy,
27462306a36Sopenharmony_ci	.match	  = em_ipt_match,
27562306a36Sopenharmony_ci	.dump	  = em_ipt_dump,
27662306a36Sopenharmony_ci	.owner	  = THIS_MODULE,
27762306a36Sopenharmony_ci	.link	  = LIST_HEAD_INIT(em_ipt_ops.link)
27862306a36Sopenharmony_ci};
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_cistatic int __init init_em_ipt(void)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	return tcf_em_register(&em_ipt_ops);
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic void __exit exit_em_ipt(void)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	tcf_em_unregister(&em_ipt_ops);
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
29162306a36Sopenharmony_ciMODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>");
29262306a36Sopenharmony_ciMODULE_DESCRIPTION("TC extended match for IPtables matches");
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cimodule_init(init_em_ipt);
29562306a36Sopenharmony_cimodule_exit(exit_em_ipt);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ciMODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT);
298