Commit | Line | Data |
---|---|---|
d2912cb1 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
9ba1f726 | 2 | /* |
cd727514 | 3 | * Copyright (c) 2014 Arturo Borrero Gonzalez <arturo@debian.org> |
9ba1f726 AB |
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_tables.h> | |
13 | #include <net/netfilter/nf_nat.h> | |
bf8981a2 | 14 | #include <net/netfilter/nf_nat_masquerade.h> |
9ba1f726 | 15 | |
a9ce849e FW |
16 | struct nft_masq { |
17 | u32 flags; | |
4f16d25c PNA |
18 | u8 sreg_proto_min; |
19 | u8 sreg_proto_max; | |
a9ce849e FW |
20 | }; |
21 | ||
22 | static const struct nla_policy nft_masq_policy[NFTA_MASQ_MAX + 1] = { | |
8a6bf5da PNA |
23 | [NFTA_MASQ_FLAGS] = { .type = NLA_U32 }, |
24 | [NFTA_MASQ_REG_PROTO_MIN] = { .type = NLA_U32 }, | |
25 | [NFTA_MASQ_REG_PROTO_MAX] = { .type = NLA_U32 }, | |
9ba1f726 | 26 | }; |
9ba1f726 | 27 | |
a9ce849e FW |
28 | static int nft_masq_validate(const struct nft_ctx *ctx, |
29 | const struct nft_expr *expr, | |
30 | const struct nft_data **data) | |
75e8d06d PNA |
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_POST_ROUTING)); | |
40 | } | |
75e8d06d | 41 | |
a9ce849e FW |
42 | static int nft_masq_init(const struct nft_ctx *ctx, |
43 | const struct nft_expr *expr, | |
44 | const struct nlattr * const tb[]) | |
9ba1f726 | 45 | { |
c593642c | 46 | u32 plen = sizeof_field(struct nf_nat_range, min_addr.all); |
9ba1f726 | 47 | struct nft_masq *priv = nft_expr_priv(expr); |
7210e4e3 PNA |
48 | int err; |
49 | ||
8a6bf5da PNA |
50 | if (tb[NFTA_MASQ_FLAGS]) { |
51 | priv->flags = ntohl(nla_get_be32(tb[NFTA_MASQ_FLAGS])); | |
52 | if (priv->flags & ~NF_NAT_RANGE_MASK) | |
53 | return -EINVAL; | |
54 | } | |
55 | ||
56 | if (tb[NFTA_MASQ_REG_PROTO_MIN]) { | |
4f16d25c PNA |
57 | err = nft_parse_register_load(tb[NFTA_MASQ_REG_PROTO_MIN], |
58 | &priv->sreg_proto_min, plen); | |
8a6bf5da PNA |
59 | if (err < 0) |
60 | return err; | |
61 | ||
62 | if (tb[NFTA_MASQ_REG_PROTO_MAX]) { | |
4f16d25c PNA |
63 | err = nft_parse_register_load(tb[NFTA_MASQ_REG_PROTO_MAX], |
64 | &priv->sreg_proto_max, | |
65 | plen); | |
8a6bf5da PNA |
66 | if (err < 0) |
67 | return err; | |
68 | } else { | |
69 | priv->sreg_proto_max = priv->sreg_proto_min; | |
70 | } | |
71 | } | |
9ba1f726 | 72 | |
36596dad | 73 | return nf_ct_netns_get(ctx->net, ctx->family); |
9ba1f726 | 74 | } |
9ba1f726 | 75 | |
7d34aa3e PS |
76 | static int nft_masq_dump(struct sk_buff *skb, |
77 | const struct nft_expr *expr, bool reset) | |
9ba1f726 AB |
78 | { |
79 | const struct nft_masq *priv = nft_expr_priv(expr); | |
80 | ||
8a6bf5da PNA |
81 | if (priv->flags != 0 && |
82 | nla_put_be32(skb, NFTA_MASQ_FLAGS, htonl(priv->flags))) | |
9ba1f726 AB |
83 | goto nla_put_failure; |
84 | ||
8a6bf5da PNA |
85 | if (priv->sreg_proto_min) { |
86 | if (nft_dump_register(skb, NFTA_MASQ_REG_PROTO_MIN, | |
87 | priv->sreg_proto_min) || | |
88 | nft_dump_register(skb, NFTA_MASQ_REG_PROTO_MAX, | |
89 | priv->sreg_proto_max)) | |
90 | goto nla_put_failure; | |
91 | } | |
92 | ||
9ba1f726 AB |
93 | return 0; |
94 | ||
95 | nla_put_failure: | |
96 | return -1; | |
97 | } | |
a9ce849e FW |
98 | |
99 | static void nft_masq_ipv4_eval(const struct nft_expr *expr, | |
100 | struct nft_regs *regs, | |
101 | const struct nft_pktinfo *pkt) | |
102 | { | |
103 | struct nft_masq *priv = nft_expr_priv(expr); | |
104 | struct nf_nat_range2 range; | |
105 | ||
106 | memset(&range, 0, sizeof(range)); | |
107 | range.flags = priv->flags; | |
108 | if (priv->sreg_proto_min) { | |
109 | range.min_proto.all = (__force __be16)nft_reg_load16( | |
110 | ®s->data[priv->sreg_proto_min]); | |
111 | range.max_proto.all = (__force __be16)nft_reg_load16( | |
112 | ®s->data[priv->sreg_proto_max]); | |
113 | } | |
114 | regs->verdict.code = nf_nat_masquerade_ipv4(pkt->skb, nft_hook(pkt), | |
115 | &range, nft_out(pkt)); | |
116 | } | |
117 | ||
118 | static void | |
119 | nft_masq_ipv4_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr) | |
120 | { | |
121 | nf_ct_netns_put(ctx->net, NFPROTO_IPV4); | |
122 | } | |
123 | ||
124 | static struct nft_expr_type nft_masq_ipv4_type; | |
125 | static const struct nft_expr_ops nft_masq_ipv4_ops = { | |
126 | .type = &nft_masq_ipv4_type, | |
127 | .size = NFT_EXPR_SIZE(sizeof(struct nft_masq)), | |
128 | .eval = nft_masq_ipv4_eval, | |
129 | .init = nft_masq_init, | |
130 | .destroy = nft_masq_ipv4_destroy, | |
131 | .dump = nft_masq_dump, | |
132 | .validate = nft_masq_validate, | |
b2d30654 | 133 | .reduce = NFT_REDUCE_READONLY, |
a9ce849e FW |
134 | }; |
135 | ||
136 | static struct nft_expr_type nft_masq_ipv4_type __read_mostly = { | |
137 | .family = NFPROTO_IPV4, | |
138 | .name = "masq", | |
139 | .ops = &nft_masq_ipv4_ops, | |
140 | .policy = nft_masq_policy, | |
141 | .maxattr = NFTA_MASQ_MAX, | |
142 | .owner = THIS_MODULE, | |
143 | }; | |
144 | ||
145 | #ifdef CONFIG_NF_TABLES_IPV6 | |
146 | static void nft_masq_ipv6_eval(const struct nft_expr *expr, | |
147 | struct nft_regs *regs, | |
148 | const struct nft_pktinfo *pkt) | |
149 | { | |
150 | struct nft_masq *priv = nft_expr_priv(expr); | |
151 | struct nf_nat_range2 range; | |
152 | ||
153 | memset(&range, 0, sizeof(range)); | |
154 | range.flags = priv->flags; | |
155 | if (priv->sreg_proto_min) { | |
156 | range.min_proto.all = (__force __be16)nft_reg_load16( | |
157 | ®s->data[priv->sreg_proto_min]); | |
158 | range.max_proto.all = (__force __be16)nft_reg_load16( | |
159 | ®s->data[priv->sreg_proto_max]); | |
160 | } | |
161 | regs->verdict.code = nf_nat_masquerade_ipv6(pkt->skb, &range, | |
162 | nft_out(pkt)); | |
163 | } | |
164 | ||
165 | static void | |
166 | nft_masq_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_masq_ipv6_type; | |
172 | static const struct nft_expr_ops nft_masq_ipv6_ops = { | |
173 | .type = &nft_masq_ipv6_type, | |
174 | .size = NFT_EXPR_SIZE(sizeof(struct nft_masq)), | |
175 | .eval = nft_masq_ipv6_eval, | |
176 | .init = nft_masq_init, | |
177 | .destroy = nft_masq_ipv6_destroy, | |
178 | .dump = nft_masq_dump, | |
179 | .validate = nft_masq_validate, | |
b2d30654 | 180 | .reduce = NFT_REDUCE_READONLY, |
a9ce849e FW |
181 | }; |
182 | ||
183 | static struct nft_expr_type nft_masq_ipv6_type __read_mostly = { | |
184 | .family = NFPROTO_IPV6, | |
185 | .name = "masq", | |
186 | .ops = &nft_masq_ipv6_ops, | |
187 | .policy = nft_masq_policy, | |
188 | .maxattr = NFTA_MASQ_MAX, | |
189 | .owner = THIS_MODULE, | |
190 | }; | |
191 | ||
192 | static int __init nft_masq_module_init_ipv6(void) | |
193 | { | |
610a4314 | 194 | return nft_register_expr(&nft_masq_ipv6_type); |
a9ce849e FW |
195 | } |
196 | ||
197 | static void nft_masq_module_exit_ipv6(void) | |
198 | { | |
199 | nft_unregister_expr(&nft_masq_ipv6_type); | |
a9ce849e FW |
200 | } |
201 | #else | |
202 | static inline int nft_masq_module_init_ipv6(void) { return 0; } | |
203 | static inline void nft_masq_module_exit_ipv6(void) {} | |
204 | #endif | |
205 | ||
071657d2 FW |
206 | #ifdef CONFIG_NF_TABLES_INET |
207 | static void nft_masq_inet_eval(const struct nft_expr *expr, | |
208 | struct nft_regs *regs, | |
209 | const struct nft_pktinfo *pkt) | |
210 | { | |
211 | switch (nft_pf(pkt)) { | |
212 | case NFPROTO_IPV4: | |
213 | return nft_masq_ipv4_eval(expr, regs, pkt); | |
214 | case NFPROTO_IPV6: | |
215 | return nft_masq_ipv6_eval(expr, regs, pkt); | |
216 | } | |
217 | ||
218 | WARN_ON_ONCE(1); | |
219 | } | |
220 | ||
221 | static void | |
222 | nft_masq_inet_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr) | |
223 | { | |
224 | nf_ct_netns_put(ctx->net, NFPROTO_INET); | |
225 | } | |
226 | ||
227 | static struct nft_expr_type nft_masq_inet_type; | |
228 | static const struct nft_expr_ops nft_masq_inet_ops = { | |
229 | .type = &nft_masq_inet_type, | |
230 | .size = NFT_EXPR_SIZE(sizeof(struct nft_masq)), | |
231 | .eval = nft_masq_inet_eval, | |
232 | .init = nft_masq_init, | |
233 | .destroy = nft_masq_inet_destroy, | |
234 | .dump = nft_masq_dump, | |
235 | .validate = nft_masq_validate, | |
b2d30654 | 236 | .reduce = NFT_REDUCE_READONLY, |
071657d2 FW |
237 | }; |
238 | ||
239 | static struct nft_expr_type nft_masq_inet_type __read_mostly = { | |
240 | .family = NFPROTO_INET, | |
241 | .name = "masq", | |
242 | .ops = &nft_masq_inet_ops, | |
243 | .policy = nft_masq_policy, | |
244 | .maxattr = NFTA_MASQ_MAX, | |
245 | .owner = THIS_MODULE, | |
246 | }; | |
247 | ||
248 | static int __init nft_masq_module_init_inet(void) | |
249 | { | |
250 | return nft_register_expr(&nft_masq_inet_type); | |
251 | } | |
252 | ||
253 | static void nft_masq_module_exit_inet(void) | |
254 | { | |
255 | nft_unregister_expr(&nft_masq_inet_type); | |
256 | } | |
257 | #else | |
258 | static inline int nft_masq_module_init_inet(void) { return 0; } | |
259 | static inline void nft_masq_module_exit_inet(void) {} | |
260 | #endif | |
261 | ||
a9ce849e FW |
262 | static int __init nft_masq_module_init(void) |
263 | { | |
264 | int ret; | |
265 | ||
266 | ret = nft_masq_module_init_ipv6(); | |
267 | if (ret < 0) | |
268 | return ret; | |
269 | ||
071657d2 FW |
270 | ret = nft_masq_module_init_inet(); |
271 | if (ret < 0) { | |
272 | nft_masq_module_exit_ipv6(); | |
273 | return ret; | |
274 | } | |
275 | ||
a9ce849e FW |
276 | ret = nft_register_expr(&nft_masq_ipv4_type); |
277 | if (ret < 0) { | |
071657d2 | 278 | nft_masq_module_exit_inet(); |
a9ce849e FW |
279 | nft_masq_module_exit_ipv6(); |
280 | return ret; | |
281 | } | |
282 | ||
610a4314 | 283 | ret = nf_nat_masquerade_inet_register_notifiers(); |
a9ce849e FW |
284 | if (ret < 0) { |
285 | nft_masq_module_exit_ipv6(); | |
071657d2 | 286 | nft_masq_module_exit_inet(); |
a9ce849e FW |
287 | nft_unregister_expr(&nft_masq_ipv4_type); |
288 | return ret; | |
289 | } | |
290 | ||
291 | return ret; | |
292 | } | |
293 | ||
294 | static void __exit nft_masq_module_exit(void) | |
295 | { | |
296 | nft_masq_module_exit_ipv6(); | |
071657d2 | 297 | nft_masq_module_exit_inet(); |
a9ce849e | 298 | nft_unregister_expr(&nft_masq_ipv4_type); |
610a4314 | 299 | nf_nat_masquerade_inet_unregister_notifiers(); |
a9ce849e FW |
300 | } |
301 | ||
302 | module_init(nft_masq_module_init); | |
303 | module_exit(nft_masq_module_exit); | |
9ba1f726 AB |
304 | |
305 | MODULE_LICENSE("GPL"); | |
cd727514 | 306 | MODULE_AUTHOR("Arturo Borrero Gonzalez <arturo@debian.org>"); |
5142967a | 307 | MODULE_ALIAS_NFT_EXPR("masq"); |
4cacc395 | 308 | MODULE_DESCRIPTION("Netfilter nftables masquerade expression support"); |