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