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 | |
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(chain: ctx->chain, type: NFT_CHAIN_T_NAT); |
36 | if (err < 0) |
37 | return err; |
38 | |
39 | return nft_chain_validate_hooks(chain: ctx->chain, |
40 | hook_flags: (1 << NF_INET_PRE_ROUTING) | |
41 | (1 << NF_INET_LOCAL_OUT)); |
42 | } |
43 | |
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(attr: tb[NFTA_REDIR_REG_PROTO_MIN], |
55 | sreg: &priv->sreg_proto_min, len: plen); |
56 | if (err < 0) |
57 | return err; |
58 | |
59 | if (tb[NFTA_REDIR_REG_PROTO_MAX]) { |
60 | err = nft_parse_register_load(attr: tb[NFTA_REDIR_REG_PROTO_MAX], |
61 | sreg: &priv->sreg_proto_max, |
62 | len: 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(net: ctx->net, nfproto: ctx->family); |
76 | } |
77 | |
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, attr: NFTA_REDIR_REG_PROTO_MIN, |
85 | reg: priv->sreg_proto_min)) |
86 | goto nla_put_failure; |
87 | if (nft_dump_register(skb, attr: NFTA_REDIR_REG_PROTO_MAX, |
88 | reg: priv->sreg_proto_max)) |
89 | goto nla_put_failure; |
90 | } |
91 | |
92 | if (priv->flags != 0 && |
93 | nla_put_be32(skb, attrtype: 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 | |
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(sreg: ®s->data[priv->sreg_proto_min]); |
114 | range.max_proto.all = (__force __be16) |
115 | nft_reg_load16(sreg: ®s->data[priv->sreg_proto_max]); |
116 | } |
117 | |
118 | switch (nft_pf(pkt)) { |
119 | case NFPROTO_IPV4: |
120 | regs->verdict.code = nf_nat_redirect_ipv4(skb: pkt->skb, range: &range, |
121 | hooknum: nft_hook(pkt)); |
122 | break; |
123 | #ifdef CONFIG_NF_TABLES_IPV6 |
124 | case NFPROTO_IPV6: |
125 | regs->verdict.code = nf_nat_redirect_ipv6(skb: pkt->skb, range: &range, |
126 | hooknum: nft_hook(pkt)); |
127 | break; |
128 | #endif |
129 | default: |
130 | WARN_ON_ONCE(1); |
131 | break; |
132 | } |
133 | } |
134 | |
135 | static void |
136 | nft_redir_ipv4_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr) |
137 | { |
138 | nf_ct_netns_put(net: ctx->net, nfproto: 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 |
164 | nft_redir_ipv6_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr) |
165 | { |
166 | nf_ct_netns_put(net: ctx->net, nfproto: 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 |
193 | nft_redir_inet_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr) |
194 | { |
195 | nf_ct_netns_put(net: ctx->net, nfproto: 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 | |
219 | static int __init nft_redir_module_init_inet(void) |
220 | { |
221 | return nft_register_expr(&nft_redir_inet_type); |
222 | } |
223 | #else |
224 | static inline int nft_redir_module_init_inet(void) { return 0; } |
225 | #endif |
226 | |
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 | |
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 | |