Merge tag 'scsi-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/jejb/scsi
[platform/kernel/linux-rpi.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 };
218
219 static int nft_exthdr_init(const struct nft_ctx *ctx,
220                            const struct nft_expr *expr,
221                            const struct nlattr * const tb[])
222 {
223         struct nft_exthdr *priv = nft_expr_priv(expr);
224         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
225         int err;
226
227         if (!tb[NFTA_EXTHDR_DREG] ||
228             !tb[NFTA_EXTHDR_TYPE] ||
229             !tb[NFTA_EXTHDR_OFFSET] ||
230             !tb[NFTA_EXTHDR_LEN])
231                 return -EINVAL;
232
233         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
234         if (err < 0)
235                 return err;
236
237         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
238         if (err < 0)
239                 return err;
240
241         if (tb[NFTA_EXTHDR_FLAGS]) {
242                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
243                 if (err < 0)
244                         return err;
245
246                 if (flags & ~NFT_EXTHDR_F_PRESENT)
247                         return -EINVAL;
248         }
249
250         if (tb[NFTA_EXTHDR_OP]) {
251                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
252                 if (err < 0)
253                         return err;
254         }
255
256         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
257         priv->offset = offset;
258         priv->len    = len;
259         priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
260         priv->flags  = flags;
261         priv->op     = op;
262
263         return nft_validate_register_store(ctx, priv->dreg, NULL,
264                                            NFT_DATA_VALUE, priv->len);
265 }
266
267 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
268                                    const struct nft_expr *expr,
269                                    const struct nlattr * const tb[])
270 {
271         struct nft_exthdr *priv = nft_expr_priv(expr);
272         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
273         int err;
274
275         if (!tb[NFTA_EXTHDR_SREG] ||
276             !tb[NFTA_EXTHDR_TYPE] ||
277             !tb[NFTA_EXTHDR_OFFSET] ||
278             !tb[NFTA_EXTHDR_LEN])
279                 return -EINVAL;
280
281         if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
282                 return -EINVAL;
283
284         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
285         if (err < 0)
286                 return err;
287
288         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
289         if (err < 0)
290                 return err;
291
292         if (offset < 2)
293                 return -EOPNOTSUPP;
294
295         switch (len) {
296         case 2: break;
297         case 4: break;
298         default:
299                 return -EOPNOTSUPP;
300         }
301
302         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
303         if (err < 0)
304                 return err;
305
306         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
307         priv->offset = offset;
308         priv->len    = len;
309         priv->sreg   = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
310         priv->flags  = flags;
311         priv->op     = op;
312
313         return nft_validate_register_load(priv->sreg, priv->len);
314 }
315
316 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
317 {
318         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
319                 goto nla_put_failure;
320         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
321                 goto nla_put_failure;
322         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
323                 goto nla_put_failure;
324         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
325                 goto nla_put_failure;
326         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
327                 goto nla_put_failure;
328         return 0;
329
330 nla_put_failure:
331         return -1;
332 }
333
334 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
335 {
336         const struct nft_exthdr *priv = nft_expr_priv(expr);
337
338         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
339                 return -1;
340
341         return nft_exthdr_dump_common(skb, priv);
342 }
343
344 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
345 {
346         const struct nft_exthdr *priv = nft_expr_priv(expr);
347
348         if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
349                 return -1;
350
351         return nft_exthdr_dump_common(skb, priv);
352 }
353
354 static struct nft_expr_type nft_exthdr_type;
355 static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
356         .type           = &nft_exthdr_type,
357         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
358         .eval           = nft_exthdr_ipv6_eval,
359         .init           = nft_exthdr_init,
360         .dump           = nft_exthdr_dump,
361 };
362
363 static const struct nft_expr_ops nft_exthdr_tcp_ops = {
364         .type           = &nft_exthdr_type,
365         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
366         .eval           = nft_exthdr_tcp_eval,
367         .init           = nft_exthdr_init,
368         .dump           = nft_exthdr_dump,
369 };
370
371 static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
372         .type           = &nft_exthdr_type,
373         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
374         .eval           = nft_exthdr_tcp_set_eval,
375         .init           = nft_exthdr_tcp_set_init,
376         .dump           = nft_exthdr_dump_set,
377 };
378
379 static const struct nft_expr_ops *
380 nft_exthdr_select_ops(const struct nft_ctx *ctx,
381                       const struct nlattr * const tb[])
382 {
383         u32 op;
384
385         if (!tb[NFTA_EXTHDR_OP])
386                 return &nft_exthdr_ipv6_ops;
387
388         if (tb[NFTA_EXTHDR_SREG] && tb[NFTA_EXTHDR_DREG])
389                 return ERR_PTR(-EOPNOTSUPP);
390
391         op = ntohl(nla_get_be32(tb[NFTA_EXTHDR_OP]));
392         switch (op) {
393         case NFT_EXTHDR_OP_TCPOPT:
394                 if (tb[NFTA_EXTHDR_SREG])
395                         return &nft_exthdr_tcp_set_ops;
396                 if (tb[NFTA_EXTHDR_DREG])
397                         return &nft_exthdr_tcp_ops;
398                 break;
399         case NFT_EXTHDR_OP_IPV6:
400                 if (tb[NFTA_EXTHDR_DREG])
401                         return &nft_exthdr_ipv6_ops;
402                 break;
403         }
404
405         return ERR_PTR(-EOPNOTSUPP);
406 }
407
408 static struct nft_expr_type nft_exthdr_type __read_mostly = {
409         .name           = "exthdr",
410         .select_ops     = nft_exthdr_select_ops,
411         .policy         = nft_exthdr_policy,
412         .maxattr        = NFTA_EXTHDR_MAX,
413         .owner          = THIS_MODULE,
414 };
415
416 static int __init nft_exthdr_module_init(void)
417 {
418         return nft_register_expr(&nft_exthdr_type);
419 }
420
421 static void __exit nft_exthdr_module_exit(void)
422 {
423         nft_unregister_expr(&nft_exthdr_type);
424 }
425
426 module_init(nft_exthdr_module_init);
427 module_exit(nft_exthdr_module_exit);
428
429 MODULE_LICENSE("GPL");
430 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
431 MODULE_ALIAS_NFT_EXPR("exthdr");