47ec1046ad11536e337f709d1f41a267a77cf1d3
[sfrench/cifs-2.6.git] / net / netfilter / nft_exthdr.c
1 /*
2  * Copyright (c) 2008 Patrick McHardy <kaber@trash.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation.
7  *
8  * Development of this code funded by Astaro AG (http://www.astaro.com/)
9  */
10
11 #include <asm/unaligned.h>
12 #include <linux/kernel.h>
13 #include <linux/init.h>
14 #include <linux/module.h>
15 #include <linux/netlink.h>
16 #include <linux/netfilter.h>
17 #include <linux/netfilter/nf_tables.h>
18 #include <net/netfilter/nf_tables.h>
19 #include <net/tcp.h>
20
21 struct nft_exthdr {
22         u8                      type;
23         u8                      offset;
24         u8                      len;
25         u8                      op;
26         enum nft_registers      dreg:8;
27         enum nft_registers      sreg:8;
28         u8                      flags;
29 };
30
31 static unsigned int optlen(const u8 *opt, unsigned int offset)
32 {
33         /* Beware zero-length options: make finite progress */
34         if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
35                 return 1;
36         else
37                 return opt[offset + 1];
38 }
39
40 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
41                                  struct nft_regs *regs,
42                                  const struct nft_pktinfo *pkt)
43 {
44         struct nft_exthdr *priv = nft_expr_priv(expr);
45         u32 *dest = &regs->data[priv->dreg];
46         unsigned int offset = 0;
47         int err;
48
49         err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
50         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
51                 *dest = (err >= 0);
52                 return;
53         } else if (err < 0) {
54                 goto err;
55         }
56         offset += priv->offset;
57
58         dest[priv->len / NFT_REG32_SIZE] = 0;
59         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
60                 goto err;
61         return;
62 err:
63         regs->verdict.code = NFT_BREAK;
64 }
65
66 static void *
67 nft_tcp_header_pointer(const struct nft_pktinfo *pkt,
68                        unsigned int len, void *buffer, unsigned int *tcphdr_len)
69 {
70         struct tcphdr *tcph;
71
72         if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
73                 return NULL;
74
75         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer);
76         if (!tcph)
77                 return NULL;
78
79         *tcphdr_len = __tcp_hdrlen(tcph);
80         if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len)
81                 return NULL;
82
83         return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer);
84 }
85
86 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
87                                 struct nft_regs *regs,
88                                 const struct nft_pktinfo *pkt)
89 {
90         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
91         struct nft_exthdr *priv = nft_expr_priv(expr);
92         unsigned int i, optl, tcphdr_len, offset;
93         u32 *dest = &regs->data[priv->dreg];
94         struct tcphdr *tcph;
95         u8 *opt;
96
97         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
98         if (!tcph)
99                 goto err;
100
101         opt = (u8 *)tcph;
102         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
103                 optl = optlen(opt, i);
104
105                 if (priv->type != opt[i])
106                         continue;
107
108                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
109                         goto err;
110
111                 offset = i + priv->offset;
112                 if (priv->flags & NFT_EXTHDR_F_PRESENT) {
113                         *dest = 1;
114                 } else {
115                         dest[priv->len / NFT_REG32_SIZE] = 0;
116                         memcpy(dest, opt + offset, priv->len);
117                 }
118
119                 return;
120         }
121
122 err:
123         if (priv->flags & NFT_EXTHDR_F_PRESENT)
124                 *dest = 0;
125         else
126                 regs->verdict.code = NFT_BREAK;
127 }
128
129 static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
130                                     struct nft_regs *regs,
131                                     const struct nft_pktinfo *pkt)
132 {
133         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
134         struct nft_exthdr *priv = nft_expr_priv(expr);
135         unsigned int i, optl, tcphdr_len, offset;
136         struct tcphdr *tcph;
137         u8 *opt;
138         u32 src;
139
140         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
141         if (!tcph)
142                 return;
143
144         opt = (u8 *)tcph;
145         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
146                 union {
147                         u8 octet;
148                         __be16 v16;
149                         __be32 v32;
150                 } old, new;
151
152                 optl = optlen(opt, i);
153
154                 if (priv->type != opt[i])
155                         continue;
156
157                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
158                         return;
159
160                 if (!skb_make_writable(pkt->skb, pkt->xt.thoff + i + priv->len))
161                         return;
162
163                 tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
164                                               &tcphdr_len);
165                 if (!tcph)
166                         return;
167
168                 src = regs->data[priv->sreg];
169                 offset = i + priv->offset;
170
171                 switch (priv->len) {
172                 case 2:
173                         old.v16 = get_unaligned((u16 *)(opt + offset));
174                         new.v16 = src;
175
176                         switch (priv->type) {
177                         case TCPOPT_MSS:
178                                 /* increase can cause connection to stall */
179                                 if (ntohs(old.v16) <= ntohs(new.v16))
180                                         return;
181                         break;
182                         }
183
184                         if (old.v16 == new.v16)
185                                 return;
186
187                         put_unaligned(new.v16, (u16*)(opt + offset));
188                         inet_proto_csum_replace2(&tcph->check, pkt->skb,
189                                                  old.v16, new.v16, false);
190                         break;
191                 case 4:
192                         new.v32 = src;
193                         old.v32 = get_unaligned((u32 *)(opt + offset));
194
195                         if (old.v32 == new.v32)
196                                 return;
197
198                         put_unaligned(new.v32, (u32*)(opt + offset));
199                         inet_proto_csum_replace4(&tcph->check, pkt->skb,
200                                                  old.v32, new.v32, false);
201                         break;
202                 default:
203                         WARN_ON_ONCE(1);
204                         break;
205                 }
206
207                 return;
208         }
209 }
210
211 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
212         [NFTA_EXTHDR_DREG]              = { .type = NLA_U32 },
213         [NFTA_EXTHDR_TYPE]              = { .type = NLA_U8 },
214         [NFTA_EXTHDR_OFFSET]            = { .type = NLA_U32 },
215         [NFTA_EXTHDR_LEN]               = { .type = NLA_U32 },
216         [NFTA_EXTHDR_FLAGS]             = { .type = NLA_U32 },
217         [NFTA_EXTHDR_OP]                = { .type = NLA_U32 },
218         [NFTA_EXTHDR_SREG]              = { .type = NLA_U32 },
219 };
220
221 static int nft_exthdr_init(const struct nft_ctx *ctx,
222                            const struct nft_expr *expr,
223                            const struct nlattr * const tb[])
224 {
225         struct nft_exthdr *priv = nft_expr_priv(expr);
226         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
227         int err;
228
229         if (!tb[NFTA_EXTHDR_DREG] ||
230             !tb[NFTA_EXTHDR_TYPE] ||
231             !tb[NFTA_EXTHDR_OFFSET] ||
232             !tb[NFTA_EXTHDR_LEN])
233                 return -EINVAL;
234
235         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
236         if (err < 0)
237                 return err;
238
239         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
240         if (err < 0)
241                 return err;
242
243         if (tb[NFTA_EXTHDR_FLAGS]) {
244                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
245                 if (err < 0)
246                         return err;
247
248                 if (flags & ~NFT_EXTHDR_F_PRESENT)
249                         return -EINVAL;
250         }
251
252         if (tb[NFTA_EXTHDR_OP]) {
253                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
254                 if (err < 0)
255                         return err;
256         }
257
258         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
259         priv->offset = offset;
260         priv->len    = len;
261         priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
262         priv->flags  = flags;
263         priv->op     = op;
264
265         return nft_validate_register_store(ctx, priv->dreg, NULL,
266                                            NFT_DATA_VALUE, priv->len);
267 }
268
269 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
270                                    const struct nft_expr *expr,
271                                    const struct nlattr * const tb[])
272 {
273         struct nft_exthdr *priv = nft_expr_priv(expr);
274         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
275         int err;
276
277         if (!tb[NFTA_EXTHDR_SREG] ||
278             !tb[NFTA_EXTHDR_TYPE] ||
279             !tb[NFTA_EXTHDR_OFFSET] ||
280             !tb[NFTA_EXTHDR_LEN])
281                 return -EINVAL;
282
283         if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
284                 return -EINVAL;
285
286         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
287         if (err < 0)
288                 return err;
289
290         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
291         if (err < 0)
292                 return err;
293
294         if (offset < 2)
295                 return -EOPNOTSUPP;
296
297         switch (len) {
298         case 2: break;
299         case 4: break;
300         default:
301                 return -EOPNOTSUPP;
302         }
303
304         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
305         if (err < 0)
306                 return err;
307
308         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
309         priv->offset = offset;
310         priv->len    = len;
311         priv->sreg   = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
312         priv->flags  = flags;
313         priv->op     = op;
314
315         return nft_validate_register_load(priv->sreg, priv->len);
316 }
317
318 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
319 {
320         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
321                 goto nla_put_failure;
322         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
323                 goto nla_put_failure;
324         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
325                 goto nla_put_failure;
326         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
327                 goto nla_put_failure;
328         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
329                 goto nla_put_failure;
330         return 0;
331
332 nla_put_failure:
333         return -1;
334 }
335
336 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
337 {
338         const struct nft_exthdr *priv = nft_expr_priv(expr);
339
340         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
341                 return -1;
342
343         return nft_exthdr_dump_common(skb, priv);
344 }
345
346 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
347 {
348         const struct nft_exthdr *priv = nft_expr_priv(expr);
349
350         if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
351                 return -1;
352
353         return nft_exthdr_dump_common(skb, priv);
354 }
355
356 static struct nft_expr_type nft_exthdr_type;
357 static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
358         .type           = &nft_exthdr_type,
359         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
360         .eval           = nft_exthdr_ipv6_eval,
361         .init           = nft_exthdr_init,
362         .dump           = nft_exthdr_dump,
363 };
364
365 static const struct nft_expr_ops nft_exthdr_tcp_ops = {
366         .type           = &nft_exthdr_type,
367         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
368         .eval           = nft_exthdr_tcp_eval,
369         .init           = nft_exthdr_init,
370         .dump           = nft_exthdr_dump,
371 };
372
373 static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
374         .type           = &nft_exthdr_type,
375         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
376         .eval           = nft_exthdr_tcp_set_eval,
377         .init           = nft_exthdr_tcp_set_init,
378         .dump           = nft_exthdr_dump_set,
379 };
380
381 static const struct nft_expr_ops *
382 nft_exthdr_select_ops(const struct nft_ctx *ctx,
383                       const struct nlattr * const tb[])
384 {
385         u32 op;
386
387         if (!tb[NFTA_EXTHDR_OP])
388                 return &nft_exthdr_ipv6_ops;
389
390         if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG])
391                 return ERR_PTR(-EOPNOTSUPP);
392
393         op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP]));
394         switch (op) {
395         case NFT_EXTHDR_OP_TCPOPT:
396                 if (tb[NFTA_EXTHDR_SREG])
397                         return &nft_exthdr_tcp_set_ops;
398                 if (tb[NFTA_EXTHDR_DREG])
399                         return &nft_exthdr_tcp_ops;
400                 break;
401         case NFT_EXTHDR_OP_IPV6:
402                 if (tb[NFTA_EXTHDR_DREG])
403                         return &nft_exthdr_ipv6_ops;
404                 break;
405         }
406
407         return ERR_PTR(-EOPNOTSUPP);
408 }
409
410 static struct nft_expr_type nft_exthdr_type __read_mostly = {
411         .name           = "exthdr",
412         .select_ops     = nft_exthdr_select_ops,
413         .policy         = nft_exthdr_policy,
414         .maxattr        = NFTA_EXTHDR_MAX,
415         .owner          = THIS_MODULE,
416 };
417
418 static int __init nft_exthdr_module_init(void)
419 {
420         return nft_register_expr(&nft_exthdr_type);
421 }
422
423 static void __exit nft_exthdr_module_exit(void)
424 {
425         nft_unregister_expr(&nft_exthdr_type);
426 }
427
428 module_init(nft_exthdr_module_init);
429 module_exit(nft_exthdr_module_exit);
430
431 MODULE_LICENSE("GPL");
432 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
433 MODULE_ALIAS_NFT_EXPR("exthdr");