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