1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Transparent proxy support for Linux/iptables |
4 | * |
5 | * Copyright (c) 2006-2010 BalaBit IT Ltd. |
6 | * Author: Balazs Scheidler, Krisztian Kovacs |
7 | */ |
8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
9 | #include <linux/module.h> |
10 | #include <linux/skbuff.h> |
11 | #include <linux/ip.h> |
12 | #include <net/checksum.h> |
13 | #include <net/udp.h> |
14 | #include <net/tcp.h> |
15 | #include <net/inet_sock.h> |
16 | #include <net/inet_hashtables.h> |
17 | #include <linux/inetdevice.h> |
18 | #include <linux/netfilter/x_tables.h> |
19 | #include <linux/netfilter_ipv4/ip_tables.h> |
20 | |
21 | #include <net/netfilter/ipv4/nf_defrag_ipv4.h> |
22 | |
23 | #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
24 | #define XT_TPROXY_HAVE_IPV6 1 |
25 | #include <net/if_inet6.h> |
26 | #include <net/addrconf.h> |
27 | #include <net/inet6_hashtables.h> |
28 | #include <linux/netfilter_ipv6/ip6_tables.h> |
29 | #include <net/netfilter/ipv6/nf_defrag_ipv6.h> |
30 | #endif |
31 | |
32 | #include <net/netfilter/nf_tproxy.h> |
33 | #include <linux/netfilter/xt_TPROXY.h> |
34 | |
35 | static unsigned int |
36 | tproxy_tg4(struct net *net, struct sk_buff *skb, __be32 laddr, __be16 lport, |
37 | u_int32_t mark_mask, u_int32_t mark_value) |
38 | { |
39 | const struct iphdr *iph = ip_hdr(skb); |
40 | struct udphdr _hdr, *hp; |
41 | struct sock *sk; |
42 | |
43 | hp = skb_header_pointer(skb, offset: ip_hdrlen(skb), len: sizeof(_hdr), buffer: &_hdr); |
44 | if (hp == NULL) |
45 | return NF_DROP; |
46 | |
47 | /* check if there's an ongoing connection on the packet |
48 | * addresses, this happens if the redirect already happened |
49 | * and the current packet belongs to an already established |
50 | * connection */ |
51 | sk = nf_tproxy_get_sock_v4(net, skb, protocol: iph->protocol, |
52 | saddr: iph->saddr, daddr: iph->daddr, |
53 | sport: hp->source, dport: hp->dest, |
54 | in: skb->dev, lookup_type: NF_TPROXY_LOOKUP_ESTABLISHED); |
55 | |
56 | laddr = nf_tproxy_laddr4(skb, user_laddr: laddr, daddr: iph->daddr); |
57 | if (!lport) |
58 | lport = hp->dest; |
59 | |
60 | /* UDP has no TCP_TIME_WAIT state, so we never enter here */ |
61 | if (sk && sk->sk_state == TCP_TIME_WAIT) |
62 | /* reopening a TIME_WAIT connection needs special handling */ |
63 | sk = nf_tproxy_handle_time_wait4(net, skb, laddr, lport, sk); |
64 | else if (!sk) |
65 | /* no, there's no established connection, check if |
66 | * there's a listener on the redirected addr/port */ |
67 | sk = nf_tproxy_get_sock_v4(net, skb, protocol: iph->protocol, |
68 | saddr: iph->saddr, daddr: laddr, |
69 | sport: hp->source, dport: lport, |
70 | in: skb->dev, lookup_type: NF_TPROXY_LOOKUP_LISTENER); |
71 | |
72 | /* NOTE: assign_sock consumes our sk reference */ |
73 | if (sk && nf_tproxy_sk_is_transparent(sk)) { |
74 | /* This should be in a separate target, but we don't do multiple |
75 | targets on the same rule yet */ |
76 | skb->mark = (skb->mark & ~mark_mask) ^ mark_value; |
77 | nf_tproxy_assign_sock(skb, sk); |
78 | return NF_ACCEPT; |
79 | } |
80 | |
81 | return NF_DROP; |
82 | } |
83 | |
84 | static unsigned int |
85 | tproxy_tg4_v0(struct sk_buff *skb, const struct xt_action_param *par) |
86 | { |
87 | const struct xt_tproxy_target_info *tgi = par->targinfo; |
88 | |
89 | return tproxy_tg4(net: xt_net(par), skb, laddr: tgi->laddr, lport: tgi->lport, |
90 | mark_mask: tgi->mark_mask, mark_value: tgi->mark_value); |
91 | } |
92 | |
93 | static unsigned int |
94 | tproxy_tg4_v1(struct sk_buff *skb, const struct xt_action_param *par) |
95 | { |
96 | const struct xt_tproxy_target_info_v1 *tgi = par->targinfo; |
97 | |
98 | return tproxy_tg4(net: xt_net(par), skb, laddr: tgi->laddr.ip, lport: tgi->lport, |
99 | mark_mask: tgi->mark_mask, mark_value: tgi->mark_value); |
100 | } |
101 | |
102 | #ifdef XT_TPROXY_HAVE_IPV6 |
103 | |
104 | static unsigned int |
105 | tproxy_tg6_v1(struct sk_buff *skb, const struct xt_action_param *par) |
106 | { |
107 | const struct ipv6hdr *iph = ipv6_hdr(skb); |
108 | const struct xt_tproxy_target_info_v1 *tgi = par->targinfo; |
109 | struct udphdr _hdr, *hp; |
110 | struct sock *sk; |
111 | const struct in6_addr *laddr; |
112 | __be16 lport; |
113 | int thoff = 0; |
114 | int tproto; |
115 | |
116 | tproto = ipv6_find_hdr(skb, offset: &thoff, target: -1, NULL, NULL); |
117 | if (tproto < 0) |
118 | return NF_DROP; |
119 | |
120 | hp = skb_header_pointer(skb, offset: thoff, len: sizeof(_hdr), buffer: &_hdr); |
121 | if (!hp) |
122 | return NF_DROP; |
123 | |
124 | /* check if there's an ongoing connection on the packet |
125 | * addresses, this happens if the redirect already happened |
126 | * and the current packet belongs to an already established |
127 | * connection */ |
128 | sk = nf_tproxy_get_sock_v6(net: xt_net(par), skb, thoff, protocol: tproto, |
129 | saddr: &iph->saddr, daddr: &iph->daddr, |
130 | sport: hp->source, dport: hp->dest, |
131 | in: xt_in(par), lookup_type: NF_TPROXY_LOOKUP_ESTABLISHED); |
132 | |
133 | laddr = nf_tproxy_laddr6(skb, user_laddr: &tgi->laddr.in6, daddr: &iph->daddr); |
134 | lport = tgi->lport ? tgi->lport : hp->dest; |
135 | |
136 | /* UDP has no TCP_TIME_WAIT state, so we never enter here */ |
137 | if (sk && sk->sk_state == TCP_TIME_WAIT) { |
138 | const struct xt_tproxy_target_info_v1 *tgi = par->targinfo; |
139 | /* reopening a TIME_WAIT connection needs special handling */ |
140 | sk = nf_tproxy_handle_time_wait6(skb, tproto, thoff, |
141 | net: xt_net(par), |
142 | laddr: &tgi->laddr.in6, |
143 | lport: tgi->lport, |
144 | sk); |
145 | } |
146 | else if (!sk) |
147 | /* no there's no established connection, check if |
148 | * there's a listener on the redirected addr/port */ |
149 | sk = nf_tproxy_get_sock_v6(net: xt_net(par), skb, thoff, |
150 | protocol: tproto, saddr: &iph->saddr, daddr: laddr, |
151 | sport: hp->source, dport: lport, |
152 | in: xt_in(par), lookup_type: NF_TPROXY_LOOKUP_LISTENER); |
153 | |
154 | /* NOTE: assign_sock consumes our sk reference */ |
155 | if (sk && nf_tproxy_sk_is_transparent(sk)) { |
156 | /* This should be in a separate target, but we don't do multiple |
157 | targets on the same rule yet */ |
158 | skb->mark = (skb->mark & ~tgi->mark_mask) ^ tgi->mark_value; |
159 | nf_tproxy_assign_sock(skb, sk); |
160 | return NF_ACCEPT; |
161 | } |
162 | |
163 | return NF_DROP; |
164 | } |
165 | |
166 | static int tproxy_tg6_check(const struct xt_tgchk_param *par) |
167 | { |
168 | const struct ip6t_ip6 *i = par->entryinfo; |
169 | int err; |
170 | |
171 | err = nf_defrag_ipv6_enable(net: par->net); |
172 | if (err) |
173 | return err; |
174 | |
175 | if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP) && |
176 | !(i->invflags & IP6T_INV_PROTO)) |
177 | return 0; |
178 | |
179 | pr_info_ratelimited("Can be used only with -p tcp or -p udp\n" ); |
180 | return -EINVAL; |
181 | } |
182 | |
183 | static void tproxy_tg6_destroy(const struct xt_tgdtor_param *par) |
184 | { |
185 | nf_defrag_ipv6_disable(net: par->net); |
186 | } |
187 | #endif |
188 | |
189 | static int tproxy_tg4_check(const struct xt_tgchk_param *par) |
190 | { |
191 | const struct ipt_ip *i = par->entryinfo; |
192 | int err; |
193 | |
194 | err = nf_defrag_ipv4_enable(net: par->net); |
195 | if (err) |
196 | return err; |
197 | |
198 | if ((i->proto == IPPROTO_TCP || i->proto == IPPROTO_UDP) |
199 | && !(i->invflags & IPT_INV_PROTO)) |
200 | return 0; |
201 | |
202 | pr_info_ratelimited("Can be used only with -p tcp or -p udp\n" ); |
203 | return -EINVAL; |
204 | } |
205 | |
206 | static void tproxy_tg4_destroy(const struct xt_tgdtor_param *par) |
207 | { |
208 | nf_defrag_ipv4_disable(net: par->net); |
209 | } |
210 | |
211 | static struct xt_target tproxy_tg_reg[] __read_mostly = { |
212 | { |
213 | .name = "TPROXY" , |
214 | .family = NFPROTO_IPV4, |
215 | .table = "mangle" , |
216 | .target = tproxy_tg4_v0, |
217 | .revision = 0, |
218 | .targetsize = sizeof(struct xt_tproxy_target_info), |
219 | .checkentry = tproxy_tg4_check, |
220 | .destroy = tproxy_tg4_destroy, |
221 | .hooks = 1 << NF_INET_PRE_ROUTING, |
222 | .me = THIS_MODULE, |
223 | }, |
224 | { |
225 | .name = "TPROXY" , |
226 | .family = NFPROTO_IPV4, |
227 | .table = "mangle" , |
228 | .target = tproxy_tg4_v1, |
229 | .revision = 1, |
230 | .targetsize = sizeof(struct xt_tproxy_target_info_v1), |
231 | .checkentry = tproxy_tg4_check, |
232 | .destroy = tproxy_tg4_destroy, |
233 | .hooks = 1 << NF_INET_PRE_ROUTING, |
234 | .me = THIS_MODULE, |
235 | }, |
236 | #ifdef XT_TPROXY_HAVE_IPV6 |
237 | { |
238 | .name = "TPROXY" , |
239 | .family = NFPROTO_IPV6, |
240 | .table = "mangle" , |
241 | .target = tproxy_tg6_v1, |
242 | .revision = 1, |
243 | .targetsize = sizeof(struct xt_tproxy_target_info_v1), |
244 | .checkentry = tproxy_tg6_check, |
245 | .destroy = tproxy_tg6_destroy, |
246 | .hooks = 1 << NF_INET_PRE_ROUTING, |
247 | .me = THIS_MODULE, |
248 | }, |
249 | #endif |
250 | |
251 | }; |
252 | |
253 | static int __init tproxy_tg_init(void) |
254 | { |
255 | return xt_register_targets(target: tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg)); |
256 | } |
257 | |
258 | static void __exit tproxy_tg_exit(void) |
259 | { |
260 | xt_unregister_targets(target: tproxy_tg_reg, ARRAY_SIZE(tproxy_tg_reg)); |
261 | } |
262 | |
263 | module_init(tproxy_tg_init); |
264 | module_exit(tproxy_tg_exit); |
265 | MODULE_LICENSE("GPL" ); |
266 | MODULE_AUTHOR("Balazs Scheidler, Krisztian Kovacs" ); |
267 | MODULE_DESCRIPTION("Netfilter transparent proxy (TPROXY) target module." ); |
268 | MODULE_ALIAS("ipt_TPROXY" ); |
269 | MODULE_ALIAS("ip6t_TPROXY" ); |
270 | |