Merge tag 'nfs-for-5.11-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
[linux-2.6-microblaze.git] / net / netfilter / nft_redir.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2014 Arturo Borrero Gonzalez <arturo@debian.org>
4  */
5
6 #include <linux/kernel.h>
7 #include <linux/init.h>
8 #include <linux/module.h>
9 #include <linux/netlink.h>
10 #include <linux/netfilter.h>
11 #include <linux/netfilter/nf_tables.h>
12 #include <net/netfilter/nf_nat.h>
13 #include <net/netfilter/nf_nat_redirect.h>
14 #include <net/netfilter/nf_tables.h>
15
16 struct nft_redir {
17         enum nft_registers      sreg_proto_min:8;
18         enum nft_registers      sreg_proto_max:8;
19         u16                     flags;
20 };
21
22 static const struct nla_policy nft_redir_policy[NFTA_REDIR_MAX + 1] = {
23         [NFTA_REDIR_REG_PROTO_MIN]      = { .type = NLA_U32 },
24         [NFTA_REDIR_REG_PROTO_MAX]      = { .type = NLA_U32 },
25         [NFTA_REDIR_FLAGS]              = { .type = NLA_U32 },
26 };
27
28 static int nft_redir_validate(const struct nft_ctx *ctx,
29                               const struct nft_expr *expr,
30                               const struct nft_data **data)
31 {
32         int err;
33
34         err = nft_chain_validate_dependency(ctx->chain, NFT_CHAIN_T_NAT);
35         if (err < 0)
36                 return err;
37
38         return nft_chain_validate_hooks(ctx->chain,
39                                         (1 << NF_INET_PRE_ROUTING) |
40                                         (1 << NF_INET_LOCAL_OUT));
41 }
42
43 static int nft_redir_init(const struct nft_ctx *ctx,
44                           const struct nft_expr *expr,
45                           const struct nlattr * const tb[])
46 {
47         struct nft_redir *priv = nft_expr_priv(expr);
48         unsigned int plen;
49         int err;
50
51         plen = sizeof_field(struct nf_nat_range, min_addr.all);
52         if (tb[NFTA_REDIR_REG_PROTO_MIN]) {
53                 priv->sreg_proto_min =
54                         nft_parse_register(tb[NFTA_REDIR_REG_PROTO_MIN]);
55
56                 err = nft_validate_register_load(priv->sreg_proto_min, plen);
57                 if (err < 0)
58                         return err;
59
60                 if (tb[NFTA_REDIR_REG_PROTO_MAX]) {
61                         priv->sreg_proto_max =
62                                 nft_parse_register(tb[NFTA_REDIR_REG_PROTO_MAX]);
63
64                         err = nft_validate_register_load(priv->sreg_proto_max,
65                                                          plen);
66                         if (err < 0)
67                                 return err;
68                 } else {
69                         priv->sreg_proto_max = priv->sreg_proto_min;
70                 }
71         }
72
73         if (tb[NFTA_REDIR_FLAGS]) {
74                 priv->flags = ntohl(nla_get_be32(tb[NFTA_REDIR_FLAGS]));
75                 if (priv->flags & ~NF_NAT_RANGE_MASK)
76                         return -EINVAL;
77         }
78
79         return nf_ct_netns_get(ctx->net, ctx->family);
80 }
81
82 static int nft_redir_dump(struct sk_buff *skb, const struct nft_expr *expr)
83 {
84         const struct nft_redir *priv = nft_expr_priv(expr);
85
86         if (priv->sreg_proto_min) {
87                 if (nft_dump_register(skb, NFTA_REDIR_REG_PROTO_MIN,
88                                       priv->sreg_proto_min))
89                         goto nla_put_failure;
90                 if (nft_dump_register(skb, NFTA_REDIR_REG_PROTO_MAX,
91                                       priv->sreg_proto_max))
92                         goto nla_put_failure;
93         }
94
95         if (priv->flags != 0 &&
96             nla_put_be32(skb, NFTA_REDIR_FLAGS, htonl(priv->flags)))
97                         goto nla_put_failure;
98
99         return 0;
100
101 nla_put_failure:
102         return -1;
103 }
104
105 static void nft_redir_ipv4_eval(const struct nft_expr *expr,
106                                 struct nft_regs *regs,
107                                 const struct nft_pktinfo *pkt)
108 {
109         struct nft_redir *priv = nft_expr_priv(expr);
110         struct nf_nat_ipv4_multi_range_compat mr;
111
112         memset(&mr, 0, sizeof(mr));
113         if (priv->sreg_proto_min) {
114                 mr.range[0].min.all = (__force __be16)nft_reg_load16(
115                         &regs->data[priv->sreg_proto_min]);
116                 mr.range[0].max.all = (__force __be16)nft_reg_load16(
117                         &regs->data[priv->sreg_proto_max]);
118                 mr.range[0].flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
119         }
120
121         mr.range[0].flags |= priv->flags;
122
123         regs->verdict.code = nf_nat_redirect_ipv4(pkt->skb, &mr, nft_hook(pkt));
124 }
125
126 static void
127 nft_redir_ipv4_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
128 {
129         nf_ct_netns_put(ctx->net, NFPROTO_IPV4);
130 }
131
132 static struct nft_expr_type nft_redir_ipv4_type;
133 static const struct nft_expr_ops nft_redir_ipv4_ops = {
134         .type           = &nft_redir_ipv4_type,
135         .size           = NFT_EXPR_SIZE(sizeof(struct nft_redir)),
136         .eval           = nft_redir_ipv4_eval,
137         .init           = nft_redir_init,
138         .destroy        = nft_redir_ipv4_destroy,
139         .dump           = nft_redir_dump,
140         .validate       = nft_redir_validate,
141 };
142
143 static struct nft_expr_type nft_redir_ipv4_type __read_mostly = {
144         .family         = NFPROTO_IPV4,
145         .name           = "redir",
146         .ops            = &nft_redir_ipv4_ops,
147         .policy         = nft_redir_policy,
148         .maxattr        = NFTA_REDIR_MAX,
149         .owner          = THIS_MODULE,
150 };
151
152 #ifdef CONFIG_NF_TABLES_IPV6
153 static void nft_redir_ipv6_eval(const struct nft_expr *expr,
154                                 struct nft_regs *regs,
155                                 const struct nft_pktinfo *pkt)
156 {
157         struct nft_redir *priv = nft_expr_priv(expr);
158         struct nf_nat_range2 range;
159
160         memset(&range, 0, sizeof(range));
161         if (priv->sreg_proto_min) {
162                 range.min_proto.all = (__force __be16)nft_reg_load16(
163                         &regs->data[priv->sreg_proto_min]);
164                 range.max_proto.all = (__force __be16)nft_reg_load16(
165                         &regs->data[priv->sreg_proto_max]);
166                 range.flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
167         }
168
169         range.flags |= priv->flags;
170
171         regs->verdict.code =
172                 nf_nat_redirect_ipv6(pkt->skb, &range, nft_hook(pkt));
173 }
174
175 static void
176 nft_redir_ipv6_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
177 {
178         nf_ct_netns_put(ctx->net, NFPROTO_IPV6);
179 }
180
181 static struct nft_expr_type nft_redir_ipv6_type;
182 static const struct nft_expr_ops nft_redir_ipv6_ops = {
183         .type           = &nft_redir_ipv6_type,
184         .size           = NFT_EXPR_SIZE(sizeof(struct nft_redir)),
185         .eval           = nft_redir_ipv6_eval,
186         .init           = nft_redir_init,
187         .destroy        = nft_redir_ipv6_destroy,
188         .dump           = nft_redir_dump,
189         .validate       = nft_redir_validate,
190 };
191
192 static struct nft_expr_type nft_redir_ipv6_type __read_mostly = {
193         .family         = NFPROTO_IPV6,
194         .name           = "redir",
195         .ops            = &nft_redir_ipv6_ops,
196         .policy         = nft_redir_policy,
197         .maxattr        = NFTA_REDIR_MAX,
198         .owner          = THIS_MODULE,
199 };
200 #endif
201
202 #ifdef CONFIG_NF_TABLES_INET
203 static void nft_redir_inet_eval(const struct nft_expr *expr,
204                                 struct nft_regs *regs,
205                                 const struct nft_pktinfo *pkt)
206 {
207         switch (nft_pf(pkt)) {
208         case NFPROTO_IPV4:
209                 return nft_redir_ipv4_eval(expr, regs, pkt);
210         case NFPROTO_IPV6:
211                 return nft_redir_ipv6_eval(expr, regs, pkt);
212         }
213
214         WARN_ON_ONCE(1);
215 }
216
217 static void
218 nft_redir_inet_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
219 {
220         nf_ct_netns_put(ctx->net, NFPROTO_INET);
221 }
222
223 static struct nft_expr_type nft_redir_inet_type;
224 static const struct nft_expr_ops nft_redir_inet_ops = {
225         .type           = &nft_redir_inet_type,
226         .size           = NFT_EXPR_SIZE(sizeof(struct nft_redir)),
227         .eval           = nft_redir_inet_eval,
228         .init           = nft_redir_init,
229         .destroy        = nft_redir_inet_destroy,
230         .dump           = nft_redir_dump,
231         .validate       = nft_redir_validate,
232 };
233
234 static struct nft_expr_type nft_redir_inet_type __read_mostly = {
235         .family         = NFPROTO_INET,
236         .name           = "redir",
237         .ops            = &nft_redir_inet_ops,
238         .policy         = nft_redir_policy,
239         .maxattr        = NFTA_MASQ_MAX,
240         .owner          = THIS_MODULE,
241 };
242
243 static int __init nft_redir_module_init_inet(void)
244 {
245         return nft_register_expr(&nft_redir_inet_type);
246 }
247 #else
248 static inline int nft_redir_module_init_inet(void) { return 0; }
249 #endif
250
251 static int __init nft_redir_module_init(void)
252 {
253         int ret = nft_register_expr(&nft_redir_ipv4_type);
254
255         if (ret)
256                 return ret;
257
258 #ifdef CONFIG_NF_TABLES_IPV6
259         ret = nft_register_expr(&nft_redir_ipv6_type);
260         if (ret) {
261                 nft_unregister_expr(&nft_redir_ipv4_type);
262                 return ret;
263         }
264 #endif
265
266         ret = nft_redir_module_init_inet();
267         if (ret < 0) {
268                 nft_unregister_expr(&nft_redir_ipv4_type);
269 #ifdef CONFIG_NF_TABLES_IPV6
270                 nft_unregister_expr(&nft_redir_ipv6_type);
271 #endif
272                 return ret;
273         }
274
275         return ret;
276 }
277
278 static void __exit nft_redir_module_exit(void)
279 {
280         nft_unregister_expr(&nft_redir_ipv4_type);
281 #ifdef CONFIG_NF_TABLES_IPV6
282         nft_unregister_expr(&nft_redir_ipv6_type);
283 #endif
284 #ifdef CONFIG_NF_TABLES_INET
285         nft_unregister_expr(&nft_redir_inet_type);
286 #endif
287 }
288
289 module_init(nft_redir_module_init);
290 module_exit(nft_redir_module_exit);
291
292 MODULE_LICENSE("GPL");
293 MODULE_AUTHOR("Arturo Borrero Gonzalez <arturo@debian.org>");
294 MODULE_ALIAS_NFT_EXPR("redir");
295 MODULE_DESCRIPTION("Netfilter nftables redirect support");