xref: /openbmc/linux/net/sched/em_ipt.c (revision 4b4193256c8d3bc3a5397b5cd9494c2ad386317d)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2ccc007e4SEyal Birger /*
3ccc007e4SEyal Birger  * net/sched/em_ipt.c IPtables matches Ematch
4ccc007e4SEyal Birger  *
5ccc007e4SEyal Birger  * (c) 2018 Eyal Birger <eyal.birger@gmail.com>
6ccc007e4SEyal Birger  */
7ccc007e4SEyal Birger 
8ccc007e4SEyal Birger #include <linux/gfp.h>
9ccc007e4SEyal Birger #include <linux/module.h>
10ccc007e4SEyal Birger #include <linux/types.h>
11ccc007e4SEyal Birger #include <linux/kernel.h>
12ccc007e4SEyal Birger #include <linux/string.h>
13ccc007e4SEyal Birger #include <linux/skbuff.h>
14ccc007e4SEyal Birger #include <linux/tc_ematch/tc_em_ipt.h>
15ccc007e4SEyal Birger #include <linux/netfilter.h>
16ccc007e4SEyal Birger #include <linux/netfilter/x_tables.h>
17ccc007e4SEyal Birger #include <linux/netfilter_ipv4/ip_tables.h>
18ccc007e4SEyal Birger #include <linux/netfilter_ipv6/ip6_tables.h>
19ccc007e4SEyal Birger #include <net/pkt_cls.h>
20ccc007e4SEyal Birger 
21ccc007e4SEyal Birger struct em_ipt_match {
22ccc007e4SEyal Birger 	const struct xt_match *match;
23ccc007e4SEyal Birger 	u32 hook;
24ba3d24d4SNikolay Aleksandrov 	u8 nfproto;
25b90feaffSGustavo A. R. Silva 	u8 match_data[] __aligned(8);
26ccc007e4SEyal Birger };
27ccc007e4SEyal Birger 
28ccc007e4SEyal Birger struct em_ipt_xt_match {
29ccc007e4SEyal Birger 	char *match_name;
30ccc007e4SEyal Birger 	int (*validate_match_data)(struct nlattr **tb, u8 mrev);
31ccc007e4SEyal Birger };
32ccc007e4SEyal Birger 
33ccc007e4SEyal Birger static const struct nla_policy em_ipt_policy[TCA_EM_IPT_MAX + 1] = {
34ccc007e4SEyal Birger 	[TCA_EM_IPT_MATCH_NAME]		= { .type = NLA_STRING,
35ccc007e4SEyal Birger 					    .len = XT_EXTENSION_MAXNAMELEN },
36ccc007e4SEyal Birger 	[TCA_EM_IPT_MATCH_REVISION]	= { .type = NLA_U8 },
37ccc007e4SEyal Birger 	[TCA_EM_IPT_HOOK]		= { .type = NLA_U32 },
38ccc007e4SEyal Birger 	[TCA_EM_IPT_NFPROTO]		= { .type = NLA_U8 },
39ccc007e4SEyal Birger 	[TCA_EM_IPT_MATCH_DATA]		= { .type = NLA_UNSPEC },
40ccc007e4SEyal Birger };
41ccc007e4SEyal Birger 
check_match(struct net * net,struct em_ipt_match * im,int mdata_len)42ccc007e4SEyal Birger static int check_match(struct net *net, struct em_ipt_match *im, int mdata_len)
43ccc007e4SEyal Birger {
44ccc007e4SEyal Birger 	struct xt_mtchk_param mtpar = {};
45ccc007e4SEyal Birger 	union {
46ccc007e4SEyal Birger 		struct ipt_entry e4;
47ccc007e4SEyal Birger 		struct ip6t_entry e6;
48ccc007e4SEyal Birger 	} e = {};
49ccc007e4SEyal Birger 
50ccc007e4SEyal Birger 	mtpar.net	= net;
51ccc007e4SEyal Birger 	mtpar.table	= "filter";
52ccc007e4SEyal Birger 	mtpar.hook_mask	= 1 << im->hook;
53ccc007e4SEyal Birger 	mtpar.family	= im->match->family;
54ccc007e4SEyal Birger 	mtpar.match	= im->match;
55ccc007e4SEyal Birger 	mtpar.entryinfo = &e;
56ccc007e4SEyal Birger 	mtpar.matchinfo	= (void *)im->match_data;
57ccc007e4SEyal Birger 	return xt_check_match(&mtpar, mdata_len, 0, 0);
58ccc007e4SEyal Birger }
59ccc007e4SEyal Birger 
policy_validate_match_data(struct nlattr ** tb,u8 mrev)60ccc007e4SEyal Birger static int policy_validate_match_data(struct nlattr **tb, u8 mrev)
61ccc007e4SEyal Birger {
62ccc007e4SEyal Birger 	if (mrev != 0) {
63ccc007e4SEyal Birger 		pr_err("only policy match revision 0 supported");
64ccc007e4SEyal Birger 		return -EINVAL;
65ccc007e4SEyal Birger 	}
66ccc007e4SEyal Birger 
67ccc007e4SEyal Birger 	if (nla_get_u32(tb[TCA_EM_IPT_HOOK]) != NF_INET_PRE_ROUTING) {
68ccc007e4SEyal Birger 		pr_err("policy can only be matched on NF_INET_PRE_ROUTING");
69ccc007e4SEyal Birger 		return -EINVAL;
70ccc007e4SEyal Birger 	}
71ccc007e4SEyal Birger 
72ccc007e4SEyal Birger 	return 0;
73ccc007e4SEyal Birger }
74ccc007e4SEyal Birger 
addrtype_validate_match_data(struct nlattr ** tb,u8 mrev)750c4231c7SNikolay Aleksandrov static int addrtype_validate_match_data(struct nlattr **tb, u8 mrev)
760c4231c7SNikolay Aleksandrov {
770c4231c7SNikolay Aleksandrov 	if (mrev != 1) {
780c4231c7SNikolay Aleksandrov 		pr_err("only addrtype match revision 1 supported");
790c4231c7SNikolay Aleksandrov 		return -EINVAL;
800c4231c7SNikolay Aleksandrov 	}
810c4231c7SNikolay Aleksandrov 
820c4231c7SNikolay Aleksandrov 	return 0;
830c4231c7SNikolay Aleksandrov }
840c4231c7SNikolay Aleksandrov 
85ccc007e4SEyal Birger static const struct em_ipt_xt_match em_ipt_xt_matches[] = {
86ccc007e4SEyal Birger 	{
87ccc007e4SEyal Birger 		.match_name = "policy",
88ccc007e4SEyal Birger 		.validate_match_data = policy_validate_match_data
89ccc007e4SEyal Birger 	},
900c4231c7SNikolay Aleksandrov 	{
910c4231c7SNikolay Aleksandrov 		.match_name = "addrtype",
920c4231c7SNikolay Aleksandrov 		.validate_match_data = addrtype_validate_match_data
930c4231c7SNikolay Aleksandrov 	},
94ccc007e4SEyal Birger 	{}
95ccc007e4SEyal Birger };
96ccc007e4SEyal Birger 
get_xt_match(struct nlattr ** tb)97ccc007e4SEyal Birger static struct xt_match *get_xt_match(struct nlattr **tb)
98ccc007e4SEyal Birger {
99ccc007e4SEyal Birger 	const struct em_ipt_xt_match *m;
100ccc007e4SEyal Birger 	struct nlattr *mname_attr;
101ccc007e4SEyal Birger 	u8 nfproto, mrev = 0;
102ccc007e4SEyal Birger 	int ret;
103ccc007e4SEyal Birger 
104ccc007e4SEyal Birger 	mname_attr = tb[TCA_EM_IPT_MATCH_NAME];
105ccc007e4SEyal Birger 	for (m = em_ipt_xt_matches; m->match_name; m++) {
106ccc007e4SEyal Birger 		if (!nla_strcmp(mname_attr, m->match_name))
107ccc007e4SEyal Birger 			break;
108ccc007e4SEyal Birger 	}
109ccc007e4SEyal Birger 
110ccc007e4SEyal Birger 	if (!m->match_name) {
111ccc007e4SEyal Birger 		pr_err("Unsupported xt match");
112ccc007e4SEyal Birger 		return ERR_PTR(-EINVAL);
113ccc007e4SEyal Birger 	}
114ccc007e4SEyal Birger 
115ccc007e4SEyal Birger 	if (tb[TCA_EM_IPT_MATCH_REVISION])
116ccc007e4SEyal Birger 		mrev = nla_get_u8(tb[TCA_EM_IPT_MATCH_REVISION]);
117ccc007e4SEyal Birger 
118ccc007e4SEyal Birger 	ret = m->validate_match_data(tb, mrev);
119ccc007e4SEyal Birger 	if (ret < 0)
120ccc007e4SEyal Birger 		return ERR_PTR(ret);
121ccc007e4SEyal Birger 
122ccc007e4SEyal Birger 	nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]);
123ccc007e4SEyal Birger 	return xt_request_find_match(nfproto, m->match_name, mrev);
124ccc007e4SEyal Birger }
125ccc007e4SEyal Birger 
em_ipt_change(struct net * net,void * data,int data_len,struct tcf_ematch * em)126ccc007e4SEyal Birger static int em_ipt_change(struct net *net, void *data, int data_len,
127ccc007e4SEyal Birger 			 struct tcf_ematch *em)
128ccc007e4SEyal Birger {
129ccc007e4SEyal Birger 	struct nlattr *tb[TCA_EM_IPT_MAX + 1];
130ccc007e4SEyal Birger 	struct em_ipt_match *im = NULL;
131ccc007e4SEyal Birger 	struct xt_match *match;
132ccc007e4SEyal Birger 	int mdata_len, ret;
133ba3d24d4SNikolay Aleksandrov 	u8 nfproto;
134ccc007e4SEyal Birger 
1358cb08174SJohannes Berg 	ret = nla_parse_deprecated(tb, TCA_EM_IPT_MAX, data, data_len,
1368cb08174SJohannes Berg 				   em_ipt_policy, NULL);
137ccc007e4SEyal Birger 	if (ret < 0)
138ccc007e4SEyal Birger 		return ret;
139ccc007e4SEyal Birger 
140ccc007e4SEyal Birger 	if (!tb[TCA_EM_IPT_HOOK] || !tb[TCA_EM_IPT_MATCH_NAME] ||
141ccc007e4SEyal Birger 	    !tb[TCA_EM_IPT_MATCH_DATA] || !tb[TCA_EM_IPT_NFPROTO])
142ccc007e4SEyal Birger 		return -EINVAL;
143ccc007e4SEyal Birger 
144ba3d24d4SNikolay Aleksandrov 	nfproto = nla_get_u8(tb[TCA_EM_IPT_NFPROTO]);
145ba3d24d4SNikolay Aleksandrov 	switch (nfproto) {
146ba3d24d4SNikolay Aleksandrov 	case NFPROTO_IPV4:
147ba3d24d4SNikolay Aleksandrov 	case NFPROTO_IPV6:
148ba3d24d4SNikolay Aleksandrov 		break;
149ba3d24d4SNikolay Aleksandrov 	default:
150ba3d24d4SNikolay Aleksandrov 		return -EINVAL;
151ba3d24d4SNikolay Aleksandrov 	}
152ba3d24d4SNikolay Aleksandrov 
153ccc007e4SEyal Birger 	match = get_xt_match(tb);
154ccc007e4SEyal Birger 	if (IS_ERR(match)) {
155ccc007e4SEyal Birger 		pr_err("unable to load match\n");
156ccc007e4SEyal Birger 		return PTR_ERR(match);
157ccc007e4SEyal Birger 	}
158ccc007e4SEyal Birger 
159ccc007e4SEyal Birger 	mdata_len = XT_ALIGN(nla_len(tb[TCA_EM_IPT_MATCH_DATA]));
160ccc007e4SEyal Birger 	im = kzalloc(sizeof(*im) + mdata_len, GFP_KERNEL);
161ccc007e4SEyal Birger 	if (!im) {
162ccc007e4SEyal Birger 		ret = -ENOMEM;
163ccc007e4SEyal Birger 		goto err;
164ccc007e4SEyal Birger 	}
165ccc007e4SEyal Birger 
166ccc007e4SEyal Birger 	im->match = match;
167ccc007e4SEyal Birger 	im->hook = nla_get_u32(tb[TCA_EM_IPT_HOOK]);
168ba3d24d4SNikolay Aleksandrov 	im->nfproto = nfproto;
169ccc007e4SEyal Birger 	nla_memcpy(im->match_data, tb[TCA_EM_IPT_MATCH_DATA], mdata_len);
170ccc007e4SEyal Birger 
171ccc007e4SEyal Birger 	ret = check_match(net, im, mdata_len);
172ccc007e4SEyal Birger 	if (ret)
173ccc007e4SEyal Birger 		goto err;
174ccc007e4SEyal Birger 
175ccc007e4SEyal Birger 	em->datalen = sizeof(*im) + mdata_len;
176ccc007e4SEyal Birger 	em->data = (unsigned long)im;
177ccc007e4SEyal Birger 	return 0;
178ccc007e4SEyal Birger 
179ccc007e4SEyal Birger err:
180ccc007e4SEyal Birger 	kfree(im);
181ccc007e4SEyal Birger 	module_put(match->me);
182ccc007e4SEyal Birger 	return ret;
183ccc007e4SEyal Birger }
184ccc007e4SEyal Birger 
em_ipt_destroy(struct tcf_ematch * em)185ccc007e4SEyal Birger static void em_ipt_destroy(struct tcf_ematch *em)
186ccc007e4SEyal Birger {
187ccc007e4SEyal Birger 	struct em_ipt_match *im = (void *)em->data;
188ccc007e4SEyal Birger 
189ccc007e4SEyal Birger 	if (!im)
190ccc007e4SEyal Birger 		return;
191ccc007e4SEyal Birger 
192ccc007e4SEyal Birger 	if (im->match->destroy) {
193ccc007e4SEyal Birger 		struct xt_mtdtor_param par = {
194ccc007e4SEyal Birger 			.net = em->net,
195ccc007e4SEyal Birger 			.match = im->match,
196ccc007e4SEyal Birger 			.matchinfo = im->match_data,
197ccc007e4SEyal Birger 			.family = im->match->family
198ccc007e4SEyal Birger 		};
199ccc007e4SEyal Birger 		im->match->destroy(&par);
200ccc007e4SEyal Birger 	}
201ccc007e4SEyal Birger 	module_put(im->match->me);
2023c9143d9SXu Wang 	kfree(im);
203ccc007e4SEyal Birger }
204ccc007e4SEyal Birger 
em_ipt_match(struct sk_buff * skb,struct tcf_ematch * em,struct tcf_pkt_info * info)205ccc007e4SEyal Birger static int em_ipt_match(struct sk_buff *skb, struct tcf_ematch *em,
206ccc007e4SEyal Birger 			struct tcf_pkt_info *info)
207ccc007e4SEyal Birger {
208ccc007e4SEyal Birger 	const struct em_ipt_match *im = (const void *)em->data;
209ccc007e4SEyal Birger 	struct xt_action_param acpar = {};
210ccc007e4SEyal Birger 	struct net_device *indev = NULL;
211f4c1c40cSNikolay Aleksandrov 	u8 nfproto = im->match->family;
212ccc007e4SEyal Birger 	struct nf_hook_state state;
213ccc007e4SEyal Birger 	int ret;
214ccc007e4SEyal Birger 
215*d7bf2ebeSToke Høiland-Jørgensen 	switch (skb_protocol(skb, true)) {
2169e10edd7SNikolay Aleksandrov 	case htons(ETH_P_IP):
2179e10edd7SNikolay Aleksandrov 		if (!pskb_network_may_pull(skb, sizeof(struct iphdr)))
2189e10edd7SNikolay Aleksandrov 			return 0;
219f4c1c40cSNikolay Aleksandrov 		if (nfproto == NFPROTO_UNSPEC)
220f4c1c40cSNikolay Aleksandrov 			nfproto = NFPROTO_IPV4;
2219e10edd7SNikolay Aleksandrov 		break;
2229e10edd7SNikolay Aleksandrov 	case htons(ETH_P_IPV6):
2239e10edd7SNikolay Aleksandrov 		if (!pskb_network_may_pull(skb, sizeof(struct ipv6hdr)))
2249e10edd7SNikolay Aleksandrov 			return 0;
225f4c1c40cSNikolay Aleksandrov 		if (nfproto == NFPROTO_UNSPEC)
226f4c1c40cSNikolay Aleksandrov 			nfproto = NFPROTO_IPV6;
2279e10edd7SNikolay Aleksandrov 		break;
2289e10edd7SNikolay Aleksandrov 	default:
2299e10edd7SNikolay Aleksandrov 		return 0;
2309e10edd7SNikolay Aleksandrov 	}
2319e10edd7SNikolay Aleksandrov 
232ccc007e4SEyal Birger 	rcu_read_lock();
233ccc007e4SEyal Birger 
234ccc007e4SEyal Birger 	if (skb->skb_iif)
235ccc007e4SEyal Birger 		indev = dev_get_by_index_rcu(em->net, skb->skb_iif);
236ccc007e4SEyal Birger 
237f4c1c40cSNikolay Aleksandrov 	nf_hook_state_init(&state, im->hook, nfproto,
238ccc007e4SEyal Birger 			   indev ?: skb->dev, skb->dev, NULL, em->net, NULL);
239ccc007e4SEyal Birger 
240ccc007e4SEyal Birger 	acpar.match = im->match;
241ccc007e4SEyal Birger 	acpar.matchinfo = im->match_data;
242ccc007e4SEyal Birger 	acpar.state = &state;
243ccc007e4SEyal Birger 
244ccc007e4SEyal Birger 	ret = im->match->match(skb, &acpar);
245ccc007e4SEyal Birger 
246ccc007e4SEyal Birger 	rcu_read_unlock();
247ccc007e4SEyal Birger 	return ret;
248ccc007e4SEyal Birger }
249ccc007e4SEyal Birger 
em_ipt_dump(struct sk_buff * skb,struct tcf_ematch * em)250ccc007e4SEyal Birger static int em_ipt_dump(struct sk_buff *skb, struct tcf_ematch *em)
251ccc007e4SEyal Birger {
252ccc007e4SEyal Birger 	struct em_ipt_match *im = (void *)em->data;
253ccc007e4SEyal Birger 
254ccc007e4SEyal Birger 	if (nla_put_string(skb, TCA_EM_IPT_MATCH_NAME, im->match->name) < 0)
255ccc007e4SEyal Birger 		return -EMSGSIZE;
256ccc007e4SEyal Birger 	if (nla_put_u32(skb, TCA_EM_IPT_HOOK, im->hook) < 0)
257ccc007e4SEyal Birger 		return -EMSGSIZE;
258ccc007e4SEyal Birger 	if (nla_put_u8(skb, TCA_EM_IPT_MATCH_REVISION, im->match->revision) < 0)
259ccc007e4SEyal Birger 		return -EMSGSIZE;
260ba3d24d4SNikolay Aleksandrov 	if (nla_put_u8(skb, TCA_EM_IPT_NFPROTO, im->nfproto) < 0)
261ccc007e4SEyal Birger 		return -EMSGSIZE;
262ccc007e4SEyal Birger 	if (nla_put(skb, TCA_EM_IPT_MATCH_DATA,
263ccc007e4SEyal Birger 		    im->match->usersize ?: im->match->matchsize,
264ccc007e4SEyal Birger 		    im->match_data) < 0)
265ccc007e4SEyal Birger 		return -EMSGSIZE;
266ccc007e4SEyal Birger 
267ccc007e4SEyal Birger 	return 0;
268ccc007e4SEyal Birger }
269ccc007e4SEyal Birger 
270ccc007e4SEyal Birger static struct tcf_ematch_ops em_ipt_ops = {
271ccc007e4SEyal Birger 	.kind	  = TCF_EM_IPT,
272ccc007e4SEyal Birger 	.change	  = em_ipt_change,
273ccc007e4SEyal Birger 	.destroy  = em_ipt_destroy,
274ccc007e4SEyal Birger 	.match	  = em_ipt_match,
275ccc007e4SEyal Birger 	.dump	  = em_ipt_dump,
276ccc007e4SEyal Birger 	.owner	  = THIS_MODULE,
277ccc007e4SEyal Birger 	.link	  = LIST_HEAD_INIT(em_ipt_ops.link)
278ccc007e4SEyal Birger };
279ccc007e4SEyal Birger 
init_em_ipt(void)280ccc007e4SEyal Birger static int __init init_em_ipt(void)
281ccc007e4SEyal Birger {
282ccc007e4SEyal Birger 	return tcf_em_register(&em_ipt_ops);
283ccc007e4SEyal Birger }
284ccc007e4SEyal Birger 
exit_em_ipt(void)285ccc007e4SEyal Birger static void __exit exit_em_ipt(void)
286ccc007e4SEyal Birger {
287ccc007e4SEyal Birger 	tcf_em_unregister(&em_ipt_ops);
288ccc007e4SEyal Birger }
289ccc007e4SEyal Birger 
290ccc007e4SEyal Birger MODULE_LICENSE("GPL");
291ccc007e4SEyal Birger MODULE_AUTHOR("Eyal Birger <eyal.birger@gmail.com>");
292ccc007e4SEyal Birger MODULE_DESCRIPTION("TC extended match for IPtables matches");
293ccc007e4SEyal Birger 
294ccc007e4SEyal Birger module_init(init_em_ipt);
295ccc007e4SEyal Birger module_exit(exit_em_ipt);
296ccc007e4SEyal Birger 
297ccc007e4SEyal Birger MODULE_ALIAS_TCF_EMATCH(TCF_EM_IPT);
298