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