xref: /openbmc/linux/net/netfilter/nft_redir.c (revision 06ba8020)
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 	u8			sreg_proto_min;
18 	u8			sreg_proto_max;
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_proto.all);
52 	if (tb[NFTA_REDIR_REG_PROTO_MIN]) {
53 		err = nft_parse_register_load(tb[NFTA_REDIR_REG_PROTO_MIN],
54 					      &priv->sreg_proto_min, plen);
55 		if (err < 0)
56 			return err;
57 
58 		if (tb[NFTA_REDIR_REG_PROTO_MAX]) {
59 			err = nft_parse_register_load(tb[NFTA_REDIR_REG_PROTO_MAX],
60 						      &priv->sreg_proto_max,
61 						      plen);
62 			if (err < 0)
63 				return err;
64 		} else {
65 			priv->sreg_proto_max = priv->sreg_proto_min;
66 		}
67 
68 		priv->flags |= NF_NAT_RANGE_PROTO_SPECIFIED;
69 	}
70 
71 	if (tb[NFTA_REDIR_FLAGS]) {
72 		priv->flags = ntohl(nla_get_be32(tb[NFTA_REDIR_FLAGS]));
73 		if (priv->flags & ~NF_NAT_RANGE_MASK)
74 			return -EINVAL;
75 	}
76 
77 	return nf_ct_netns_get(ctx->net, ctx->family);
78 }
79 
80 static int nft_redir_dump(struct sk_buff *skb,
81 			  const struct nft_expr *expr, bool reset)
82 {
83 	const struct nft_redir *priv = nft_expr_priv(expr);
84 
85 	if (priv->sreg_proto_min) {
86 		if (nft_dump_register(skb, NFTA_REDIR_REG_PROTO_MIN,
87 				      priv->sreg_proto_min))
88 			goto nla_put_failure;
89 		if (nft_dump_register(skb, NFTA_REDIR_REG_PROTO_MAX,
90 				      priv->sreg_proto_max))
91 			goto nla_put_failure;
92 	}
93 
94 	if (priv->flags != 0 &&
95 	    nla_put_be32(skb, NFTA_REDIR_FLAGS, htonl(priv->flags)))
96 			goto nla_put_failure;
97 
98 	return 0;
99 
100 nla_put_failure:
101 	return -1;
102 }
103 
104 static void nft_redir_eval(const struct nft_expr *expr,
105 			   struct nft_regs *regs,
106 			   const struct nft_pktinfo *pkt)
107 {
108 	const struct nft_redir *priv = nft_expr_priv(expr);
109 	struct nf_nat_range2 range;
110 
111 	memset(&range, 0, sizeof(range));
112 	range.flags = priv->flags;
113 	if (priv->sreg_proto_min) {
114 		range.min_proto.all = (__force __be16)
115 			nft_reg_load16(&regs->data[priv->sreg_proto_min]);
116 		range.max_proto.all = (__force __be16)
117 			nft_reg_load16(&regs->data[priv->sreg_proto_max]);
118 	}
119 
120 	switch (nft_pf(pkt)) {
121 	case NFPROTO_IPV4:
122 		regs->verdict.code = nf_nat_redirect_ipv4(pkt->skb, &range,
123 							  nft_hook(pkt));
124 		break;
125 #ifdef CONFIG_NF_TABLES_IPV6
126 	case NFPROTO_IPV6:
127 		regs->verdict.code = nf_nat_redirect_ipv6(pkt->skb, &range,
128 							  nft_hook(pkt));
129 		break;
130 #endif
131 	default:
132 		WARN_ON_ONCE(1);
133 		break;
134 	}
135 }
136 
137 static void
138 nft_redir_ipv4_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
139 {
140 	nf_ct_netns_put(ctx->net, NFPROTO_IPV4);
141 }
142 
143 static struct nft_expr_type nft_redir_ipv4_type;
144 static const struct nft_expr_ops nft_redir_ipv4_ops = {
145 	.type		= &nft_redir_ipv4_type,
146 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_redir)),
147 	.eval		= nft_redir_eval,
148 	.init		= nft_redir_init,
149 	.destroy	= nft_redir_ipv4_destroy,
150 	.dump		= nft_redir_dump,
151 	.validate	= nft_redir_validate,
152 	.reduce		= NFT_REDUCE_READONLY,
153 };
154 
155 static struct nft_expr_type nft_redir_ipv4_type __read_mostly = {
156 	.family		= NFPROTO_IPV4,
157 	.name		= "redir",
158 	.ops		= &nft_redir_ipv4_ops,
159 	.policy		= nft_redir_policy,
160 	.maxattr	= NFTA_REDIR_MAX,
161 	.owner		= THIS_MODULE,
162 };
163 
164 #ifdef CONFIG_NF_TABLES_IPV6
165 static void
166 nft_redir_ipv6_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
167 {
168 	nf_ct_netns_put(ctx->net, NFPROTO_IPV6);
169 }
170 
171 static struct nft_expr_type nft_redir_ipv6_type;
172 static const struct nft_expr_ops nft_redir_ipv6_ops = {
173 	.type		= &nft_redir_ipv6_type,
174 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_redir)),
175 	.eval		= nft_redir_eval,
176 	.init		= nft_redir_init,
177 	.destroy	= nft_redir_ipv6_destroy,
178 	.dump		= nft_redir_dump,
179 	.validate	= nft_redir_validate,
180 	.reduce		= NFT_REDUCE_READONLY,
181 };
182 
183 static struct nft_expr_type nft_redir_ipv6_type __read_mostly = {
184 	.family		= NFPROTO_IPV6,
185 	.name		= "redir",
186 	.ops		= &nft_redir_ipv6_ops,
187 	.policy		= nft_redir_policy,
188 	.maxattr	= NFTA_REDIR_MAX,
189 	.owner		= THIS_MODULE,
190 };
191 #endif
192 
193 #ifdef CONFIG_NF_TABLES_INET
194 static void
195 nft_redir_inet_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
196 {
197 	nf_ct_netns_put(ctx->net, NFPROTO_INET);
198 }
199 
200 static struct nft_expr_type nft_redir_inet_type;
201 static const struct nft_expr_ops nft_redir_inet_ops = {
202 	.type		= &nft_redir_inet_type,
203 	.size		= NFT_EXPR_SIZE(sizeof(struct nft_redir)),
204 	.eval		= nft_redir_eval,
205 	.init		= nft_redir_init,
206 	.destroy	= nft_redir_inet_destroy,
207 	.dump		= nft_redir_dump,
208 	.validate	= nft_redir_validate,
209 	.reduce		= NFT_REDUCE_READONLY,
210 };
211 
212 static struct nft_expr_type nft_redir_inet_type __read_mostly = {
213 	.family		= NFPROTO_INET,
214 	.name		= "redir",
215 	.ops		= &nft_redir_inet_ops,
216 	.policy		= nft_redir_policy,
217 	.maxattr	= NFTA_REDIR_MAX,
218 	.owner		= THIS_MODULE,
219 };
220 
221 static int __init nft_redir_module_init_inet(void)
222 {
223 	return nft_register_expr(&nft_redir_inet_type);
224 }
225 #else
226 static inline int nft_redir_module_init_inet(void) { return 0; }
227 #endif
228 
229 static int __init nft_redir_module_init(void)
230 {
231 	int ret = nft_register_expr(&nft_redir_ipv4_type);
232 
233 	if (ret)
234 		return ret;
235 
236 #ifdef CONFIG_NF_TABLES_IPV6
237 	ret = nft_register_expr(&nft_redir_ipv6_type);
238 	if (ret) {
239 		nft_unregister_expr(&nft_redir_ipv4_type);
240 		return ret;
241 	}
242 #endif
243 
244 	ret = nft_redir_module_init_inet();
245 	if (ret < 0) {
246 		nft_unregister_expr(&nft_redir_ipv4_type);
247 #ifdef CONFIG_NF_TABLES_IPV6
248 		nft_unregister_expr(&nft_redir_ipv6_type);
249 #endif
250 		return ret;
251 	}
252 
253 	return ret;
254 }
255 
256 static void __exit nft_redir_module_exit(void)
257 {
258 	nft_unregister_expr(&nft_redir_ipv4_type);
259 #ifdef CONFIG_NF_TABLES_IPV6
260 	nft_unregister_expr(&nft_redir_ipv6_type);
261 #endif
262 #ifdef CONFIG_NF_TABLES_INET
263 	nft_unregister_expr(&nft_redir_inet_type);
264 #endif
265 }
266 
267 module_init(nft_redir_module_init);
268 module_exit(nft_redir_module_exit);
269 
270 MODULE_LICENSE("GPL");
271 MODULE_AUTHOR("Arturo Borrero Gonzalez <arturo@debian.org>");
272 MODULE_ALIAS_NFT_EXPR("redir");
273 MODULE_DESCRIPTION("Netfilter nftables redirect support");
274