netfilter: nf_tables: merge exthdr expression into nft core
[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/netlink.h>
14 #include <linux/netfilter.h>
15 #include <linux/netfilter/nf_tables.h>
16 #include <net/netfilter/nf_tables_core.h>
17 #include <net/netfilter/nf_tables.h>
18 #include <net/tcp.h>
19
20 struct nft_exthdr {
21         u8                      type;
22         u8                      offset;
23         u8                      len;
24         u8                      op;
25         enum nft_registers      dreg:8;
26         enum nft_registers      sreg:8;
27         u8                      flags;
28 };
29
30 static unsigned int optlen(const u8 *opt, unsigned int offset)
31 {
32         /* Beware zero-length options: make finite progress */
33         if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
34                 return 1;
35         else
36                 return opt[offset + 1];
37 }
38
39 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
40                                  struct nft_regs *regs,
41                                  const struct nft_pktinfo *pkt)
42 {
43         struct nft_exthdr *priv = nft_expr_priv(expr);
44         u32 *dest = &regs->data[priv->dreg];
45         unsigned int offset = 0;
46         int err;
47
48         err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
49         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
50                 *dest = (err >= 0);
51                 return;
52         } else if (err < 0) {
53                 goto err;
54         }
55         offset += priv->offset;
56
57         dest[priv->len / NFT_REG32_SIZE] = 0;
58         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
59                 goto err;
60         return;
61 err:
62         regs->verdict.code = NFT_BREAK;
63 }
64
65 static void *
66 nft_tcp_header_pointer(const struct nft_pktinfo *pkt,
67                        unsigned int len, void *buffer, unsigned int *tcphdr_len)
68 {
69         struct tcphdr *tcph;
70
71         if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
72                 return NULL;
73
74         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buffer);
75         if (!tcph)
76                 return NULL;
77
78         *tcphdr_len = __tcp_hdrlen(tcph);
79         if (*tcphdr_len < sizeof(*tcph) || *tcphdr_len > len)
80                 return NULL;
81
82         return skb_header_pointer(pkt->skb, pkt->xt.thoff, *tcphdr_len, buffer);
83 }
84
85 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
86                                 struct nft_regs *regs,
87                                 const struct nft_pktinfo *pkt)
88 {
89         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
90         struct nft_exthdr *priv = nft_expr_priv(expr);
91         unsigned int i, optl, tcphdr_len, offset;
92         u32 *dest = &regs->data[priv->dreg];
93         struct tcphdr *tcph;
94         u8 *opt;
95
96         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
97         if (!tcph)
98                 goto err;
99
100         opt = (u8 *)tcph;
101         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
102                 optl = optlen(opt, i);
103
104                 if (priv->type != opt[i])
105                         continue;
106
107                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
108                         goto err;
109
110                 offset = i + priv->offset;
111                 if (priv->flags & NFT_EXTHDR_F_PRESENT) {
112                         *dest = 1;
113                 } else {
114                         dest[priv->len / NFT_REG32_SIZE] = 0;
115                         memcpy(dest, opt + offset, priv->len);
116                 }
117
118                 return;
119         }
120
121 err:
122         if (priv->flags & NFT_EXTHDR_F_PRESENT)
123                 *dest = 0;
124         else
125                 regs->verdict.code = NFT_BREAK;
126 }
127
128 static void nft_exthdr_tcp_set_eval(const struct nft_expr *expr,
129                                     struct nft_regs *regs,
130                                     const struct nft_pktinfo *pkt)
131 {
132         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
133         struct nft_exthdr *priv = nft_expr_priv(expr);
134         unsigned int i, optl, tcphdr_len, offset;
135         struct tcphdr *tcph;
136         u8 *opt;
137         u32 src;
138
139         tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff, &tcphdr_len);
140         if (!tcph)
141                 return;
142
143         opt = (u8 *)tcph;
144         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
145                 union {
146                         u8 octet;
147                         __be16 v16;
148                         __be32 v32;
149                 } old, new;
150
151                 optl = optlen(opt, i);
152
153                 if (priv->type != opt[i])
154                         continue;
155
156                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
157                         return;
158
159                 if (!skb_make_writable(pkt->skb, pkt->xt.thoff + i + priv->len))
160                         return;
161
162                 tcph = nft_tcp_header_pointer(pkt, sizeof(buff), buff,
163                                               &tcphdr_len);
164                 if (!tcph)
165                         return;
166
167                 src = regs->data[priv->sreg];
168                 offset = i + priv->offset;
169
170                 switch (priv->len) {
171                 case 2:
172                         old.v16 = get_unaligned((u16 *)(opt + offset));
173                         new.v16 = src;
174
175                         switch (priv->type) {
176                         case TCPOPT_MSS:
177                                 /* increase can cause connection to stall */
178                                 if (ntohs(old.v16) <= ntohs(new.v16))
179                                         return;
180                         break;
181                         }
182
183                         if (old.v16 == new.v16)
184                                 return;
185
186                         put_unaligned(new.v16, (u16*)(opt + offset));
187                         inet_proto_csum_replace2(&tcph->check, pkt->skb,
188                                                  old.v16, new.v16, false);
189                         break;
190                 case 4:
191                         new.v32 = src;
192                         old.v32 = get_unaligned((u32 *)(opt + offset));
193
194                         if (old.v32 == new.v32)
195                                 return;
196
197                         put_unaligned(new.v32, (u32*)(opt + offset));
198                         inet_proto_csum_replace4(&tcph->check, pkt->skb,
199                                                  old.v32, new.v32, false);
200                         break;
201                 default:
202                         WARN_ON_ONCE(1);
203                         break;
204                 }
205
206                 return;
207         }
208 }
209
210 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
211         [NFTA_EXTHDR_DREG]              = { .type = NLA_U32 },
212         [NFTA_EXTHDR_TYPE]              = { .type = NLA_U8 },
213         [NFTA_EXTHDR_OFFSET]            = { .type = NLA_U32 },
214         [NFTA_EXTHDR_LEN]               = { .type = NLA_U32 },
215         [NFTA_EXTHDR_FLAGS]             = { .type = NLA_U32 },
216         [NFTA_EXTHDR_OP]                = { .type = NLA_U32 },
217         [NFTA_EXTHDR_SREG]              = { .type = NLA_U32 },
218 };
219
220 static int nft_exthdr_init(const struct nft_ctx *ctx,
221                            const struct nft_expr *expr,
222                            const struct nlattr * const tb[])
223 {
224         struct nft_exthdr *priv = nft_expr_priv(expr);
225         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
226         int err;
227
228         if (!tb[NFTA_EXTHDR_DREG] ||
229             !tb[NFTA_EXTHDR_TYPE] ||
230             !tb[NFTA_EXTHDR_OFFSET] ||
231             !tb[NFTA_EXTHDR_LEN])
232                 return -EINVAL;
233
234         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
235         if (err < 0)
236                 return err;
237
238         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
239         if (err < 0)
240                 return err;
241
242         if (tb[NFTA_EXTHDR_FLAGS]) {
243                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
244                 if (err < 0)
245                         return err;
246
247                 if (flags & ~NFT_EXTHDR_F_PRESENT)
248                         return -EINVAL;
249         }
250
251         if (tb[NFTA_EXTHDR_OP]) {
252                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
253                 if (err < 0)
254                         return err;
255         }
256
257         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
258         priv->offset = offset;
259         priv->len    = len;
260         priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
261         priv->flags  = flags;
262         priv->op     = op;
263
264         return nft_validate_register_store(ctx, priv->dreg, NULL,
265                                            NFT_DATA_VALUE, priv->len);
266 }
267
268 static int nft_exthdr_tcp_set_init(const struct nft_ctx *ctx,
269                                    const struct nft_expr *expr,
270                                    const struct nlattr * const tb[])
271 {
272         struct nft_exthdr *priv = nft_expr_priv(expr);
273         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
274         int err;
275
276         if (!tb[NFTA_EXTHDR_SREG] ||
277             !tb[NFTA_EXTHDR_TYPE] ||
278             !tb[NFTA_EXTHDR_OFFSET] ||
279             !tb[NFTA_EXTHDR_LEN])
280                 return -EINVAL;
281
282         if (tb[NFTA_EXTHDR_DREG] || tb[NFTA_EXTHDR_FLAGS])
283                 return -EINVAL;
284
285         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
286         if (err < 0)
287                 return err;
288
289         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
290         if (err < 0)
291                 return err;
292
293         if (offset < 2)
294                 return -EOPNOTSUPP;
295
296         switch (len) {
297         case 2: break;
298         case 4: break;
299         default:
300                 return -EOPNOTSUPP;
301         }
302
303         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
304         if (err < 0)
305                 return err;
306
307         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
308         priv->offset = offset;
309         priv->len    = len;
310         priv->sreg   = nft_parse_register(tb[NFTA_EXTHDR_SREG]);
311         priv->flags  = flags;
312         priv->op     = op;
313
314         return nft_validate_register_load(priv->sreg, priv->len);
315 }
316
317 static int nft_exthdr_dump_common(struct sk_buff *skb, const struct nft_exthdr *priv)
318 {
319         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
320                 goto nla_put_failure;
321         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
322                 goto nla_put_failure;
323         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
324                 goto nla_put_failure;
325         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
326                 goto nla_put_failure;
327         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
328                 goto nla_put_failure;
329         return 0;
330
331 nla_put_failure:
332         return -1;
333 }
334
335 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
336 {
337         const struct nft_exthdr *priv = nft_expr_priv(expr);
338
339         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
340                 return -1;
341
342         return nft_exthdr_dump_common(skb, priv);
343 }
344
345 static int nft_exthdr_dump_set(struct sk_buff *skb, const struct nft_expr *expr)
346 {
347         const struct nft_exthdr *priv = nft_expr_priv(expr);
348
349         if (nft_dump_register(skb, NFTA_EXTHDR_SREG, priv->sreg))
350                 return -1;
351
352         return nft_exthdr_dump_common(skb, priv);
353 }
354
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 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 };