1 | /* SPDX-License-Identifier: GPL-2.0 */ |
2 | #include <linux/module.h> |
3 | #include <linux/netfilter/nf_tables.h> |
4 | #include <net/netfilter/nf_tables.h> |
5 | #include <net/netfilter/nf_tables_core.h> |
6 | #include <net/netfilter/nf_tproxy.h> |
7 | #include <net/inet_sock.h> |
8 | #include <net/tcp.h> |
9 | #include <linux/if_ether.h> |
10 | #include <net/netfilter/ipv4/nf_defrag_ipv4.h> |
11 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
12 | #include <net/netfilter/ipv6/nf_defrag_ipv6.h> |
13 | #endif |
14 | |
15 | struct nft_tproxy { |
16 | u8 sreg_addr; |
17 | u8 sreg_port; |
18 | u8 family; |
19 | }; |
20 | |
21 | static void nft_tproxy_eval_v4(const struct nft_expr *expr, |
22 | struct nft_regs *regs, |
23 | const struct nft_pktinfo *pkt) |
24 | { |
25 | const struct nft_tproxy *priv = nft_expr_priv(expr); |
26 | struct sk_buff *skb = pkt->skb; |
27 | const struct iphdr *iph = ip_hdr(skb); |
28 | struct udphdr _hdr, *hp; |
29 | __be32 taddr = 0; |
30 | __be16 tport = 0; |
31 | struct sock *sk; |
32 | |
33 | if (pkt->tprot != IPPROTO_TCP && |
34 | pkt->tprot != IPPROTO_UDP) { |
35 | regs->verdict.code = NFT_BREAK; |
36 | return; |
37 | } |
38 | |
39 | hp = skb_header_pointer(skb, offset: ip_hdrlen(skb), len: sizeof(_hdr), buffer: &_hdr); |
40 | if (!hp) { |
41 | regs->verdict.code = NFT_BREAK; |
42 | return; |
43 | } |
44 | |
45 | /* check if there's an ongoing connection on the packet addresses, this |
46 | * happens if the redirect already happened and the current packet |
47 | * belongs to an already established connection |
48 | */ |
49 | sk = nf_tproxy_get_sock_v4(net: nft_net(pkt), skb, protocol: iph->protocol, |
50 | saddr: iph->saddr, daddr: iph->daddr, |
51 | sport: hp->source, dport: hp->dest, |
52 | in: skb->dev, lookup_type: NF_TPROXY_LOOKUP_ESTABLISHED); |
53 | |
54 | if (priv->sreg_addr) |
55 | taddr = nft_reg_load_be32(sreg: ®s->data[priv->sreg_addr]); |
56 | taddr = nf_tproxy_laddr4(skb, user_laddr: taddr, daddr: iph->daddr); |
57 | |
58 | if (priv->sreg_port) |
59 | tport = nft_reg_load_be16(sreg: ®s->data[priv->sreg_port]); |
60 | if (!tport) |
61 | tport = hp->dest; |
62 | |
63 | /* UDP has no TCP_TIME_WAIT state, so we never enter here */ |
64 | if (sk && sk->sk_state == TCP_TIME_WAIT) { |
65 | /* reopening a TIME_WAIT connection needs special handling */ |
66 | sk = nf_tproxy_handle_time_wait4(net: nft_net(pkt), skb, laddr: taddr, lport: tport, sk); |
67 | } else if (!sk) { |
68 | /* no, there's no established connection, check if |
69 | * there's a listener on the redirected addr/port |
70 | */ |
71 | sk = nf_tproxy_get_sock_v4(net: nft_net(pkt), skb, protocol: iph->protocol, |
72 | saddr: iph->saddr, daddr: taddr, |
73 | sport: hp->source, dport: tport, |
74 | in: skb->dev, lookup_type: NF_TPROXY_LOOKUP_LISTENER); |
75 | } |
76 | |
77 | if (sk && nf_tproxy_sk_is_transparent(sk)) |
78 | nf_tproxy_assign_sock(skb, sk); |
79 | else |
80 | regs->verdict.code = NFT_BREAK; |
81 | } |
82 | |
83 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
84 | static void nft_tproxy_eval_v6(const struct nft_expr *expr, |
85 | struct nft_regs *regs, |
86 | const struct nft_pktinfo *pkt) |
87 | { |
88 | const struct nft_tproxy *priv = nft_expr_priv(expr); |
89 | struct sk_buff *skb = pkt->skb; |
90 | const struct ipv6hdr *iph = ipv6_hdr(skb); |
91 | int thoff = nft_thoff(pkt); |
92 | struct udphdr _hdr, *hp; |
93 | struct in6_addr taddr; |
94 | __be16 tport = 0; |
95 | struct sock *sk; |
96 | int l4proto; |
97 | |
98 | memset(&taddr, 0, sizeof(taddr)); |
99 | |
100 | if (pkt->tprot != IPPROTO_TCP && |
101 | pkt->tprot != IPPROTO_UDP) { |
102 | regs->verdict.code = NFT_BREAK; |
103 | return; |
104 | } |
105 | l4proto = pkt->tprot; |
106 | |
107 | hp = skb_header_pointer(skb, offset: thoff, len: sizeof(_hdr), buffer: &_hdr); |
108 | if (hp == NULL) { |
109 | regs->verdict.code = NFT_BREAK; |
110 | return; |
111 | } |
112 | |
113 | /* check if there's an ongoing connection on the packet addresses, this |
114 | * happens if the redirect already happened and the current packet |
115 | * belongs to an already established connection |
116 | */ |
117 | sk = nf_tproxy_get_sock_v6(net: nft_net(pkt), skb, thoff, protocol: l4proto, |
118 | saddr: &iph->saddr, daddr: &iph->daddr, |
119 | sport: hp->source, dport: hp->dest, |
120 | in: nft_in(pkt), lookup_type: NF_TPROXY_LOOKUP_ESTABLISHED); |
121 | |
122 | if (priv->sreg_addr) |
123 | memcpy(&taddr, ®s->data[priv->sreg_addr], sizeof(taddr)); |
124 | taddr = *nf_tproxy_laddr6(skb, user_laddr: &taddr, daddr: &iph->daddr); |
125 | |
126 | if (priv->sreg_port) |
127 | tport = nft_reg_load_be16(sreg: ®s->data[priv->sreg_port]); |
128 | if (!tport) |
129 | tport = hp->dest; |
130 | |
131 | /* UDP has no TCP_TIME_WAIT state, so we never enter here */ |
132 | if (sk && sk->sk_state == TCP_TIME_WAIT) { |
133 | /* reopening a TIME_WAIT connection needs special handling */ |
134 | sk = nf_tproxy_handle_time_wait6(skb, tproto: l4proto, thoff, |
135 | net: nft_net(pkt), |
136 | laddr: &taddr, |
137 | lport: tport, |
138 | sk); |
139 | } else if (!sk) { |
140 | /* no there's no established connection, check if |
141 | * there's a listener on the redirected addr/port |
142 | */ |
143 | sk = nf_tproxy_get_sock_v6(net: nft_net(pkt), skb, thoff, |
144 | protocol: l4proto, saddr: &iph->saddr, daddr: &taddr, |
145 | sport: hp->source, dport: tport, |
146 | in: nft_in(pkt), lookup_type: NF_TPROXY_LOOKUP_LISTENER); |
147 | } |
148 | |
149 | /* NOTE: assign_sock consumes our sk reference */ |
150 | if (sk && nf_tproxy_sk_is_transparent(sk)) |
151 | nf_tproxy_assign_sock(skb, sk); |
152 | else |
153 | regs->verdict.code = NFT_BREAK; |
154 | } |
155 | #endif |
156 | |
157 | static void nft_tproxy_eval(const struct nft_expr *expr, |
158 | struct nft_regs *regs, |
159 | const struct nft_pktinfo *pkt) |
160 | { |
161 | const struct nft_tproxy *priv = nft_expr_priv(expr); |
162 | |
163 | switch (nft_pf(pkt)) { |
164 | case NFPROTO_IPV4: |
165 | switch (priv->family) { |
166 | case NFPROTO_IPV4: |
167 | case NFPROTO_UNSPEC: |
168 | nft_tproxy_eval_v4(expr, regs, pkt); |
169 | return; |
170 | } |
171 | break; |
172 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
173 | case NFPROTO_IPV6: |
174 | switch (priv->family) { |
175 | case NFPROTO_IPV6: |
176 | case NFPROTO_UNSPEC: |
177 | nft_tproxy_eval_v6(expr, regs, pkt); |
178 | return; |
179 | } |
180 | #endif |
181 | } |
182 | regs->verdict.code = NFT_BREAK; |
183 | } |
184 | |
185 | static const struct nla_policy nft_tproxy_policy[NFTA_TPROXY_MAX + 1] = { |
186 | [NFTA_TPROXY_FAMILY] = NLA_POLICY_MAX(NLA_BE32, 255), |
187 | [NFTA_TPROXY_REG_ADDR] = { .type = NLA_U32 }, |
188 | [NFTA_TPROXY_REG_PORT] = { .type = NLA_U32 }, |
189 | }; |
190 | |
191 | static int nft_tproxy_init(const struct nft_ctx *ctx, |
192 | const struct nft_expr *expr, |
193 | const struct nlattr * const tb[]) |
194 | { |
195 | struct nft_tproxy *priv = nft_expr_priv(expr); |
196 | unsigned int alen = 0; |
197 | int err; |
198 | |
199 | if (!tb[NFTA_TPROXY_FAMILY] || |
200 | (!tb[NFTA_TPROXY_REG_ADDR] && !tb[NFTA_TPROXY_REG_PORT])) |
201 | return -EINVAL; |
202 | |
203 | priv->family = ntohl(nla_get_be32(tb[NFTA_TPROXY_FAMILY])); |
204 | |
205 | switch (ctx->family) { |
206 | case NFPROTO_IPV4: |
207 | if (priv->family != NFPROTO_IPV4) |
208 | return -EINVAL; |
209 | break; |
210 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
211 | case NFPROTO_IPV6: |
212 | if (priv->family != NFPROTO_IPV6) |
213 | return -EINVAL; |
214 | break; |
215 | #endif |
216 | case NFPROTO_INET: |
217 | break; |
218 | default: |
219 | return -EOPNOTSUPP; |
220 | } |
221 | |
222 | /* Address is specified but the rule family is not set accordingly */ |
223 | if (priv->family == NFPROTO_UNSPEC && tb[NFTA_TPROXY_REG_ADDR]) |
224 | return -EINVAL; |
225 | |
226 | switch (priv->family) { |
227 | case NFPROTO_IPV4: |
228 | alen = sizeof_field(union nf_inet_addr, in); |
229 | err = nf_defrag_ipv4_enable(net: ctx->net); |
230 | if (err) |
231 | return err; |
232 | break; |
233 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
234 | case NFPROTO_IPV6: |
235 | alen = sizeof_field(union nf_inet_addr, in6); |
236 | err = nf_defrag_ipv6_enable(net: ctx->net); |
237 | if (err) |
238 | return err; |
239 | break; |
240 | #endif |
241 | case NFPROTO_UNSPEC: |
242 | /* No address is specified here */ |
243 | err = nf_defrag_ipv4_enable(net: ctx->net); |
244 | if (err) |
245 | return err; |
246 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
247 | err = nf_defrag_ipv6_enable(net: ctx->net); |
248 | if (err) |
249 | return err; |
250 | #endif |
251 | break; |
252 | default: |
253 | return -EOPNOTSUPP; |
254 | } |
255 | |
256 | if (tb[NFTA_TPROXY_REG_ADDR]) { |
257 | err = nft_parse_register_load(attr: tb[NFTA_TPROXY_REG_ADDR], |
258 | sreg: &priv->sreg_addr, len: alen); |
259 | if (err < 0) |
260 | return err; |
261 | } |
262 | |
263 | if (tb[NFTA_TPROXY_REG_PORT]) { |
264 | err = nft_parse_register_load(attr: tb[NFTA_TPROXY_REG_PORT], |
265 | sreg: &priv->sreg_port, len: sizeof(u16)); |
266 | if (err < 0) |
267 | return err; |
268 | } |
269 | |
270 | return 0; |
271 | } |
272 | |
273 | static void nft_tproxy_destroy(const struct nft_ctx *ctx, |
274 | const struct nft_expr *expr) |
275 | { |
276 | const struct nft_tproxy *priv = nft_expr_priv(expr); |
277 | |
278 | switch (priv->family) { |
279 | case NFPROTO_IPV4: |
280 | nf_defrag_ipv4_disable(net: ctx->net); |
281 | break; |
282 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
283 | case NFPROTO_IPV6: |
284 | nf_defrag_ipv6_disable(net: ctx->net); |
285 | break; |
286 | #endif |
287 | case NFPROTO_UNSPEC: |
288 | nf_defrag_ipv4_disable(net: ctx->net); |
289 | #if IS_ENABLED(CONFIG_NF_TABLES_IPV6) |
290 | nf_defrag_ipv6_disable(net: ctx->net); |
291 | #endif |
292 | break; |
293 | } |
294 | } |
295 | |
296 | static int nft_tproxy_dump(struct sk_buff *skb, |
297 | const struct nft_expr *expr, bool reset) |
298 | { |
299 | const struct nft_tproxy *priv = nft_expr_priv(expr); |
300 | |
301 | if (nla_put_be32(skb, attrtype: NFTA_TPROXY_FAMILY, htonl(priv->family))) |
302 | return -1; |
303 | |
304 | if (priv->sreg_addr && |
305 | nft_dump_register(skb, attr: NFTA_TPROXY_REG_ADDR, reg: priv->sreg_addr)) |
306 | return -1; |
307 | |
308 | if (priv->sreg_port && |
309 | nft_dump_register(skb, attr: NFTA_TPROXY_REG_PORT, reg: priv->sreg_port)) |
310 | return -1; |
311 | |
312 | return 0; |
313 | } |
314 | |
315 | static int nft_tproxy_validate(const struct nft_ctx *ctx, |
316 | const struct nft_expr *expr, |
317 | const struct nft_data **data) |
318 | { |
319 | return nft_chain_validate_hooks(chain: ctx->chain, hook_flags: 1 << NF_INET_PRE_ROUTING); |
320 | } |
321 | |
322 | static struct nft_expr_type nft_tproxy_type; |
323 | static const struct nft_expr_ops nft_tproxy_ops = { |
324 | .type = &nft_tproxy_type, |
325 | .size = NFT_EXPR_SIZE(sizeof(struct nft_tproxy)), |
326 | .eval = nft_tproxy_eval, |
327 | .init = nft_tproxy_init, |
328 | .destroy = nft_tproxy_destroy, |
329 | .dump = nft_tproxy_dump, |
330 | .reduce = NFT_REDUCE_READONLY, |
331 | .validate = nft_tproxy_validate, |
332 | }; |
333 | |
334 | static struct nft_expr_type nft_tproxy_type __read_mostly = { |
335 | .name = "tproxy" , |
336 | .ops = &nft_tproxy_ops, |
337 | .policy = nft_tproxy_policy, |
338 | .maxattr = NFTA_TPROXY_MAX, |
339 | .owner = THIS_MODULE, |
340 | }; |
341 | |
342 | static int __init nft_tproxy_module_init(void) |
343 | { |
344 | return nft_register_expr(&nft_tproxy_type); |
345 | } |
346 | |
347 | static void __exit nft_tproxy_module_exit(void) |
348 | { |
349 | nft_unregister_expr(&nft_tproxy_type); |
350 | } |
351 | |
352 | module_init(nft_tproxy_module_init); |
353 | module_exit(nft_tproxy_module_exit); |
354 | |
355 | MODULE_LICENSE("GPL" ); |
356 | MODULE_AUTHOR("Máté Eckl" ); |
357 | MODULE_DESCRIPTION("nf_tables tproxy support module" ); |
358 | MODULE_ALIAS_NFT_EXPR("tproxy" ); |
359 | |