]> git.kernelconcepts.de Git - karo-tx-linux.git/blob - net/netfilter/nft_exthdr.c
tipc: remove premature ESTABLISH FSM event at link synchronization
[karo-tx-linux.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 <linux/kernel.h>
12 #include <linux/init.h>
13 #include <linux/module.h>
14 #include <linux/netlink.h>
15 #include <linux/netfilter.h>
16 #include <linux/netfilter/nf_tables.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         u8                      flags;
27 };
28
29 static unsigned int optlen(const u8 *opt, unsigned int offset)
30 {
31         /* Beware zero-length options: make finite progress */
32         if (opt[offset] <= TCPOPT_NOP || opt[offset + 1] == 0)
33                 return 1;
34         else
35                 return opt[offset + 1];
36 }
37
38 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr,
39                                  struct nft_regs *regs,
40                                  const struct nft_pktinfo *pkt)
41 {
42         struct nft_exthdr *priv = nft_expr_priv(expr);
43         u32 *dest = &regs->data[priv->dreg];
44         unsigned int offset = 0;
45         int err;
46
47         err = ipv6_find_hdr(pkt->skb, &offset, priv->type, NULL, NULL);
48         if (priv->flags & NFT_EXTHDR_F_PRESENT) {
49                 *dest = (err >= 0);
50                 return;
51         } else if (err < 0) {
52                 goto err;
53         }
54         offset += priv->offset;
55
56         dest[priv->len / NFT_REG32_SIZE] = 0;
57         if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0)
58                 goto err;
59         return;
60 err:
61         regs->verdict.code = NFT_BREAK;
62 }
63
64 static void nft_exthdr_tcp_eval(const struct nft_expr *expr,
65                                 struct nft_regs *regs,
66                                 const struct nft_pktinfo *pkt)
67 {
68         u8 buff[sizeof(struct tcphdr) + MAX_TCP_OPTION_SPACE];
69         struct nft_exthdr *priv = nft_expr_priv(expr);
70         unsigned int i, optl, tcphdr_len, offset;
71         u32 *dest = &regs->data[priv->dreg];
72         struct tcphdr *tcph;
73         u8 *opt;
74
75         if (!pkt->tprot_set || pkt->tprot != IPPROTO_TCP)
76                 goto err;
77
78         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, sizeof(*tcph), buff);
79         if (!tcph)
80                 goto err;
81
82         tcphdr_len = __tcp_hdrlen(tcph);
83         if (tcphdr_len < sizeof(*tcph))
84                 goto err;
85
86         tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff, tcphdr_len, buff);
87         if (!tcph)
88                 goto err;
89
90         opt = (u8 *)tcph;
91         for (i = sizeof(*tcph); i < tcphdr_len - 1; i += optl) {
92                 optl = optlen(opt, i);
93
94                 if (priv->type != opt[i])
95                         continue;
96
97                 if (i + optl > tcphdr_len || priv->len + priv->offset > optl)
98                         goto err;
99
100                 offset = i + priv->offset;
101                 if (priv->flags & NFT_EXTHDR_F_PRESENT) {
102                         *dest = 1;
103                 } else {
104                         dest[priv->len / NFT_REG32_SIZE] = 0;
105                         memcpy(dest, opt + offset, priv->len);
106                 }
107
108                 return;
109         }
110
111 err:
112         if (priv->flags & NFT_EXTHDR_F_PRESENT)
113                 *dest = 0;
114         else
115                 regs->verdict.code = NFT_BREAK;
116 }
117
118 static const struct nla_policy nft_exthdr_policy[NFTA_EXTHDR_MAX + 1] = {
119         [NFTA_EXTHDR_DREG]              = { .type = NLA_U32 },
120         [NFTA_EXTHDR_TYPE]              = { .type = NLA_U8 },
121         [NFTA_EXTHDR_OFFSET]            = { .type = NLA_U32 },
122         [NFTA_EXTHDR_LEN]               = { .type = NLA_U32 },
123         [NFTA_EXTHDR_FLAGS]             = { .type = NLA_U32 },
124 };
125
126 static int nft_exthdr_init(const struct nft_ctx *ctx,
127                            const struct nft_expr *expr,
128                            const struct nlattr * const tb[])
129 {
130         struct nft_exthdr *priv = nft_expr_priv(expr);
131         u32 offset, len, flags = 0, op = NFT_EXTHDR_OP_IPV6;
132         int err;
133
134         if (!tb[NFTA_EXTHDR_DREG] ||
135             !tb[NFTA_EXTHDR_TYPE] ||
136             !tb[NFTA_EXTHDR_OFFSET] ||
137             !tb[NFTA_EXTHDR_LEN])
138                 return -EINVAL;
139
140         err = nft_parse_u32_check(tb[NFTA_EXTHDR_OFFSET], U8_MAX, &offset);
141         if (err < 0)
142                 return err;
143
144         err = nft_parse_u32_check(tb[NFTA_EXTHDR_LEN], U8_MAX, &len);
145         if (err < 0)
146                 return err;
147
148         if (tb[NFTA_EXTHDR_FLAGS]) {
149                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_FLAGS], U8_MAX, &flags);
150                 if (err < 0)
151                         return err;
152
153                 if (flags & ~NFT_EXTHDR_F_PRESENT)
154                         return -EINVAL;
155         }
156
157         if (tb[NFTA_EXTHDR_OP]) {
158                 err = nft_parse_u32_check(tb[NFTA_EXTHDR_OP], U8_MAX, &op);
159                 if (err < 0)
160                         return err;
161         }
162
163         priv->type   = nla_get_u8(tb[NFTA_EXTHDR_TYPE]);
164         priv->offset = offset;
165         priv->len    = len;
166         priv->dreg   = nft_parse_register(tb[NFTA_EXTHDR_DREG]);
167         priv->flags  = flags;
168         priv->op     = op;
169
170         return nft_validate_register_store(ctx, priv->dreg, NULL,
171                                            NFT_DATA_VALUE, priv->len);
172 }
173
174 static int nft_exthdr_dump(struct sk_buff *skb, const struct nft_expr *expr)
175 {
176         const struct nft_exthdr *priv = nft_expr_priv(expr);
177
178         if (nft_dump_register(skb, NFTA_EXTHDR_DREG, priv->dreg))
179                 goto nla_put_failure;
180         if (nla_put_u8(skb, NFTA_EXTHDR_TYPE, priv->type))
181                 goto nla_put_failure;
182         if (nla_put_be32(skb, NFTA_EXTHDR_OFFSET, htonl(priv->offset)))
183                 goto nla_put_failure;
184         if (nla_put_be32(skb, NFTA_EXTHDR_LEN, htonl(priv->len)))
185                 goto nla_put_failure;
186         if (nla_put_be32(skb, NFTA_EXTHDR_FLAGS, htonl(priv->flags)))
187                 goto nla_put_failure;
188         if (nla_put_be32(skb, NFTA_EXTHDR_OP, htonl(priv->op)))
189                 goto nla_put_failure;
190         return 0;
191
192 nla_put_failure:
193         return -1;
194 }
195
196 static struct nft_expr_type nft_exthdr_type;
197 static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
198         .type           = &nft_exthdr_type,
199         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
200         .eval           = nft_exthdr_ipv6_eval,
201         .init           = nft_exthdr_init,
202         .dump           = nft_exthdr_dump,
203 };
204
205 static const struct nft_expr_ops nft_exthdr_tcp_ops = {
206         .type           = &nft_exthdr_type,
207         .size           = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
208         .eval           = nft_exthdr_tcp_eval,
209         .init           = nft_exthdr_init,
210         .dump           = nft_exthdr_dump,
211 };
212
213 static const struct nft_expr_ops *
214 nft_exthdr_select_ops(const struct nft_ctx *ctx,
215                       const struct nlattr * const tb[])
216 {
217         u32 op;
218
219         if (!tb[NFTA_EXTHDR_OP])
220                 return &nft_exthdr_ipv6_ops;
221
222         op = ntohl(nla_get_u32(tb[NFTA_EXTHDR_OP]));
223         switch (op) {
224         case NFT_EXTHDR_OP_TCPOPT:
225                 return &nft_exthdr_tcp_ops;
226         case NFT_EXTHDR_OP_IPV6:
227                 return &nft_exthdr_ipv6_ops;
228         }
229
230         return ERR_PTR(-EOPNOTSUPP);
231 }
232
233 static struct nft_expr_type nft_exthdr_type __read_mostly = {
234         .name           = "exthdr",
235         .select_ops     = nft_exthdr_select_ops,
236         .policy         = nft_exthdr_policy,
237         .maxattr        = NFTA_EXTHDR_MAX,
238         .owner          = THIS_MODULE,
239 };
240
241 static int __init nft_exthdr_module_init(void)
242 {
243         return nft_register_expr(&nft_exthdr_type);
244 }
245
246 static void __exit nft_exthdr_module_exit(void)
247 {
248         nft_unregister_expr(&nft_exthdr_type);
249 }
250
251 module_init(nft_exthdr_module_init);
252 module_exit(nft_exthdr_module_exit);
253
254 MODULE_LICENSE("GPL");
255 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
256 MODULE_ALIAS_NFT_EXPR("exthdr");