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