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