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