]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ip/Intercept.cc
Merge from trunk
[thirdparty/squid.git] / src / ip / Intercept.cc
1 /*
2 * DEBUG: section 89 NAT / IP Interception
3 * AUTHOR: Robert Collins
4 * AUTHOR: Amos Jeffries
5 *
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
8 *
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 *
32 */
33 #include "config.h"
34 #include "ip/Intercept.h"
35 #include "fde.h"
36
37 #if IPF_TRANSPARENT
38
39 #if HAVE_SYS_IOCTL_H
40 #include <sys/ioctl.h>
41 #endif
42 #if HAVE_NETINET_TCP_H
43 #include <netinet/tcp.h>
44 #endif
45 #if HAVE_NET_IF_H
46 #include <net/if.h>
47 #endif
48 #if HAVE_IPL_H
49 #include <ipl.h>
50 #elif HAVE_NETINET_IPL_H
51 #include <netinet/ipl.h>
52 #endif
53 #if HAVE_IP_FIL_COMPAT_H
54 #include <ip_fil_compat.h>
55 #elif HAVE_NETINET_IP_FIL_COMPAT_H
56 #include <netinet/ip_fil_compat.h>
57 #elif HAVE_IP_COMPAT_H
58 #include <ip_compat.h>
59 #elif HAVE_NETINET_IP_COMPAT_H
60 #include <netinet/ip_compat.h>
61 #endif
62 #if HAVE_IP_FIL_H
63 #include <ip_fil.h>
64 #elif HAVE_NETINET_IP_FIL_H
65 #include <netinet/ip_fil.h>
66 #endif
67 #if HAVE_IP_NAT_H
68 #include <ip_nat.h>
69 #elif HAVE_NETINET_IP_NAT_H
70 #include <netinet/ip_nat.h>
71 #endif
72
73 #endif /* IPF_TRANSPARENT required headers */
74
75 #if PF_TRANSPARENT
76 #include <sys/socket.h>
77 #include <sys/ioctl.h>
78 #include <sys/fcntl.h>
79 #include <net/if.h>
80 #include <netinet/in.h>
81 #if HAVE_NET_PF_PFVAR_H
82 #include <net/pf/pfvar.h>
83 #endif /* HAVE_NET_PF_PFVAR_H */
84 #if HAVE_NET_PFVAR_H
85 #include <net/pfvar.h>
86 #endif /* HAVE_NET_PFVAR_H */
87 #endif /* PF_TRANSPARENT required headers */
88
89 #if LINUX_NETFILTER
90 #include <linux/netfilter_ipv4.h>
91 #endif
92
93 // single global instance for access by other components.
94 Ip::Intercept Ip::Interceptor;
95
96 void
97 Ip::Intercept::StopTransparency(const char *str)
98 {
99 if (transparent_active) {
100 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
101 transparent_active = 0;
102 }
103 }
104
105 void
106 Ip::Intercept::StopInterception(const char *str)
107 {
108 if (intercept_active) {
109 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
110 intercept_active = 0;
111 }
112 }
113
114 int
115 Ip::Intercept::NetfilterInterception(int fd, const Ip::Address &me, Ip::Address &dst, int silent)
116 {
117 #if LINUX_NETFILTER
118 struct addrinfo *lookup = NULL;
119
120 dst.GetAddrInfo(lookup,AF_INET);
121
122 /** \par
123 * Try NAT lookup for REDIRECT or DNAT targets. */
124 if ( getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST, lookup->ai_addr, &lookup->ai_addrlen) != 0) {
125 if (!silent) {
126 debugs(89, DBG_IMPORTANT, HERE << " NF getsockopt(SO_ORIGINAL_DST) failed on FD " << fd << ": " << xstrerror());
127 last_reported = squid_curtime;
128 }
129 } else {
130 dst = *lookup;
131 }
132
133 Address::FreeAddrInfo(lookup);
134
135 if (me != dst) {
136 debugs(89, 5, HERE << "address NAT: me= " << me << ", dst= " << dst);
137 return 0;
138 }
139
140 debugs(89, 9, HERE << "address: me= " << me << ", dst= " << dst);
141 #endif
142 return -1;
143 }
144
145 int
146 Ip::Intercept::NetfilterTransparent(int fd, const Ip::Address &me, Ip::Address &client, int silent)
147 {
148 #if LINUX_NETFILTER
149
150 /* Trust the user configured properly. If not no harm done.
151 * We will simply attempt a bind outgoing on our own IP.
152 */
153 if (fd_table[fd].flags.transparent) {
154 client.SetPort(0); // allow random outgoing port to prevent address clashes
155 debugs(89, 5, HERE << "address TPROXY: me= " << me << ", client= " << client);
156 return 0;
157 }
158
159 debugs(89, 9, HERE << "address: me= " << me << ", client= " << client);
160 #endif
161 return -1;
162 }
163
164 int
165 Ip::Intercept::IpfwInterception(int fd, const Ip::Address &me, Ip::Address &dst, int silent)
166 {
167 #if IPFW_TRANSPARENT
168 struct addrinfo *lookup = NULL;
169
170 dst.GetAddrInfo(lookup,AF_INET);
171
172 /** \par
173 * Try lookup for IPFW interception. */
174 if ( getsockname(fd, lookup->ai_addr, &lookup->ai_addrlen) != 0 ) {
175 if ( !silent ) {
176 debugs(89, DBG_IMPORTANT, HERE << " IPFW getsockname(...) failed: " << xstrerror());
177 last_reported = squid_curtime;
178 }
179 } else {
180 dst = *lookup;
181 }
182
183 Address::FreeAddrInfo(lookup);
184
185 if (me != dst) {
186 debugs(89, 5, HERE << "address NAT: me= " << me << ", dst= " << dst);
187 return 0;
188 }
189
190 debugs(89, 9, HERE << "address: me= " << me << ", dst= " << dst);
191 #endif
192 return -1;
193 }
194
195 int
196 Ip::Intercept::IpfInterception(int fd, const Ip::Address &me, Ip::Address &client, Ip::Address &dst, int silent)
197 {
198 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
199
200 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
201 struct ipfobj obj;
202 #else
203 static int siocgnatl_cmd = SIOCGNATL & 0xff;
204 #endif
205 struct natlookup natLookup;
206 static int natfd = -1;
207 int x;
208
209 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
210
211 obj.ipfo_rev = IPFILTER_VERSION;
212 obj.ipfo_size = sizeof(natLookup);
213 obj.ipfo_ptr = &natLookup;
214 obj.ipfo_type = IPFOBJ_NATLOOKUP;
215 obj.ipfo_offset = 0;
216 #endif
217
218 natLookup.nl_inport = htons(me.GetPort());
219 natLookup.nl_outport = htons(dst.GetPort());
220 me.GetInAddr(natLookup.nl_inip);
221 dst.GetInAddr(natLookup.nl_outip);
222 natLookup.nl_flags = IPN_TCP;
223
224 if (natfd < 0) {
225 int save_errno;
226 enter_suid();
227 #ifdef IPNAT_NAME
228 natfd = open(IPNAT_NAME, O_RDONLY, 0);
229 #else
230 natfd = open(IPL_NAT, O_RDONLY, 0);
231 #endif
232 save_errno = errno;
233 leave_suid();
234 errno = save_errno;
235 }
236
237 if (natfd < 0) {
238 if (!silent) {
239 debugs(89, DBG_IMPORTANT, HERE << "NAT open failed: " << xstrerror());
240 last_reported = squid_curtime;
241 return -1;
242 }
243 }
244
245 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
246 x = ioctl(natfd, SIOCGNATL, &obj);
247 #else
248 /*
249 * IP-Filter changed the type for SIOCGNATL between
250 * 3.3 and 3.4. It also changed the cmd value for
251 * SIOCGNATL, so at least we can detect it. We could
252 * put something in configure and use ifdefs here, but
253 * this seems simpler.
254 */
255 if (63 == siocgnatl_cmd) {
256 struct natlookup *nlp = &natLookup;
257 x = ioctl(natfd, SIOCGNATL, &nlp);
258 } else {
259 x = ioctl(natfd, SIOCGNATL, &natLookup);
260 }
261
262 #endif
263 if (x < 0) {
264 if (errno != ESRCH) {
265 if (!silent) {
266 debugs(89, DBG_IMPORTANT, HERE << "NAT lookup failed: ioctl(SIOCGNATL)");
267 last_reported = squid_curtime;
268 }
269
270 close(natfd);
271 natfd = -1;
272 }
273
274 return -1;
275 } else {
276 if (client != natLookup.nl_realip) {
277 client = natLookup.nl_realip;
278 client.SetPort(ntohs(natLookup.nl_realport));
279 }
280 // else. we already copied it.
281
282 debugs(89, 5, HERE << "address NAT: me= " << me << ", client= " << client << ", dst= " << dst);
283 return 0;
284 }
285
286 debugs(89, 9, HERE << "address: me= " << me << ", client= " << client << ", dst= " << dst);
287
288 #endif /* --enable-ipf-transparent */
289 return -1;
290 }
291
292 int
293 Ip::Intercept::PfInterception(int fd, const Ip::Address &me, Ip::Address &client, Ip::Address &dst, int silent)
294 {
295 #if PF_TRANSPARENT /* --enable-pf-transparent */
296
297 struct pfioc_natlook nl;
298 static int pffd = -1;
299
300 if (pffd < 0)
301 pffd = open("/dev/pf", O_RDONLY);
302
303 if (pffd < 0) {
304 if (!silent) {
305 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
306 last_reported = squid_curtime;
307 }
308 return -1;
309 }
310
311 memset(&nl, 0, sizeof(struct pfioc_natlook));
312 dst.GetInAddr(nl.saddr.v4);
313 nl.sport = htons(dst.GetPort());
314
315 me.GetInAddr(nl.daddr.v4);
316 nl.dport = htons(me.GetPort());
317
318 nl.af = AF_INET;
319 nl.proto = IPPROTO_TCP;
320 nl.direction = PF_OUT;
321
322 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
323 if (errno != ENOENT) {
324 if (!silent) {
325 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
326 last_reported = squid_curtime;
327 }
328 close(pffd);
329 pffd = -1;
330 }
331 } else {
332 int natted = (client != nl.rdaddr.v4);
333 client = nl.rdaddr.v4;
334 client.SetPort(ntohs(nl.rdport));
335
336 if (natted) {
337 debugs(89, 5, HERE << "address NAT: me= " << me << ", client= " << client << ", dst= " << dst);
338 return 0;
339 }
340 }
341
342 debugs(89, 9, HERE << "address: me= " << me << ", client= " << client << ", dst= " << dst);
343
344 #endif /* --enable-pf-transparent */
345 return -1;
346 }
347
348
349 int
350 Ip::Intercept::NatLookup(int fd, const Ip::Address &me, const Ip::Address &peer, Ip::Address &client, Ip::Address &dst)
351 {
352 /* --enable-linux-netfilter */
353 /* --enable-ipfw-transparent */
354 /* --enable-ipf-transparent */
355 /* --enable-pf-transparent */
356 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
357
358 client = me;
359 dst = peer;
360
361 #if 0
362 // Crop interception errors down to one per minute.
363 int silent = (squid_curtime - last_reported > 60 ? 0 : 1);
364 #else
365 // Show all interception errors.
366 int silent = 0;
367 #endif
368
369 debugs(89, 5, HERE << "address BEGIN: me= " << me << ", client= " << client <<
370 ", dst= " << dst << ", peer= " << peer);
371
372 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
373 if (transparent_active) {
374 if ( NetfilterTransparent(fd, me, dst, silent) == 0) return 0;
375 }
376
377 /* NAT is only available in IPv4 */
378 if ( !me.IsIPv4() ) return -1;
379 if ( !peer.IsIPv4() ) return -1;
380
381 if (intercept_active) {
382 /* NAT methods that use sock-opts to return client address */
383 if ( NetfilterInterception(fd, me, client, silent) == 0) return 0;
384 if ( IpfwInterception(fd, me, client, silent) == 0) return 0;
385
386 /* NAT methods that use ioctl to return client address AND destination address */
387 if ( PfInterception(fd, me, client, dst, silent) == 0) return 0;
388 if ( IpfInterception(fd, me, client, dst, silent) == 0) return 0;
389 }
390
391 #else /* none of the transparent options configured */
392 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
393 #endif
394
395 return -1;
396 }
397
398 bool
399 Ip::Intercept::ProbeForTproxy(Ip::Address &test)
400 {
401 debugs(3, 3, "Detect TPROXY support on port " << test);
402
403 #if defined(IP_TRANSPARENT)
404
405 int tos = 1;
406 int tmp_sock = -1;
407
408 #if USE_IPV6
409 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
410 if (test.IsIPv6()) {
411 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
412
413 struct sockaddr_in6 tmp_ip6;
414 Ip::Address tmp = "::2";
415 tmp.SetPort(0);
416 tmp.GetSockAddr(tmp_ip6);
417
418 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
419 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
420 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
421
422 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
423 close(tmp_sock);
424 return true;
425 }
426 if (tmp_sock >= 0) {
427 close(tmp_sock);
428 tmp_sock = -1;
429 }
430 }
431
432 if ( test.IsIPv6() && !test.SetIPv4() ) {
433 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
434 return false;
435 }
436 #endif
437
438 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
439 if (test.IsIPv4()) {
440 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
441
442 struct sockaddr_in tmp_ip4;
443 Ip::Address tmp = "127.0.0.2";
444 tmp.SetPort(0);
445 tmp.GetSockAddr(tmp_ip4);
446
447 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
448 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
449 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
450
451 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
452 close(tmp_sock);
453 return true;
454 }
455 if (tmp_sock >= 0) {
456 close(tmp_sock);
457 }
458 }
459
460 #else /* undefined IP_TRANSPARENT */
461 debugs(3, 3, "setsockopt(IP_TRANSPARENT) not supported on this platform. Disabling TPROXYv4.");
462 #endif
463 return false;
464 }