]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/ip/Intercept.cc
2 * DEBUG: section 89 NAT / IP Interception
3 * AUTHOR: Robert Collins
4 * AUTHOR: Amos Jeffries
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
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.
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.
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.
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.
34 #include "comm/Connection.h"
36 #include "ip/Intercept.h"
37 #include "src/tools.h"
42 #include <sys/ioctl.h>
44 #if HAVE_NETINET_TCP_H
45 #include <netinet/tcp.h>
52 #elif HAVE_NETINET_IPL_H
53 #include <netinet/ipl.h>
55 #if HAVE_IP_FIL_COMPAT_H
56 #include <ip_fil_compat.h>
57 #elif HAVE_NETINET_IP_FIL_COMPAT_H
58 #include <netinet/ip_fil_compat.h>
59 #elif HAVE_IP_COMPAT_H
60 #include <ip_compat.h>
61 #elif HAVE_NETINET_IP_COMPAT_H
62 #include <netinet/ip_compat.h>
66 #elif HAVE_NETINET_IP_FIL_H
67 #include <netinet/ip_fil.h>
71 #elif HAVE_NETINET_IP_NAT_H
72 #include <netinet/ip_nat.h>
78 #endif /* IPF_TRANSPARENT required headers */
81 #include <sys/socket.h>
82 #include <sys/ioctl.h>
83 #include <sys/fcntl.h>
85 #include <netinet/in.h>
86 #if HAVE_NET_PF_PFVAR_H
87 #include <net/pf/pfvar.h>
88 #endif /* HAVE_NET_PF_PFVAR_H */
90 #include <net/pfvar.h>
91 #endif /* HAVE_NET_PFVAR_H */
92 #endif /* PF_TRANSPARENT required headers */
95 /* <climits> must be before including netfilter_ipv4.h */
98 #include <linux/netfilter_ipv4.h>
99 #if HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
100 /* 2013-07-01: Pablo the Netfilter maintainer is rejecting patches
101 * which will enable C++ compilers to build the Netfilter public headers.
102 * We can auto-detect its presence and attempt to use in case he ever
103 * changes his mind or things get cleaned up some other way.
104 * But until then are usually forced to hard-code the getsockopt() code
105 * for IPv6 NAT lookups.
107 #include <linux/netfilter_ipv6/ip6_tables.h>
109 #if !defined(IP6T_SO_ORIGINAL_DST)
110 #define IP6T_SO_ORIGINAL_DST 80 // stolen with prejudice from the above file.
112 #endif /* LINUX_NETFILTER required headers */
114 // single global instance for access by other components.
115 Ip::Intercept
Ip::Interceptor
;
118 Ip::Intercept::StopTransparency(const char *str
)
120 if (transparentActive_
) {
121 debugs(89, DBG_IMPORTANT
, "Stopping full transparency: " << str
);
122 transparentActive_
= 0;
127 Ip::Intercept::StopInterception(const char *str
)
129 if (interceptActive_
) {
130 debugs(89, DBG_IMPORTANT
, "Stopping IP interception: " << str
);
131 interceptActive_
= 0;
136 Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
139 struct sockaddr_storage lookup
;
140 socklen_t len
= newConn
->local
.isIPv6() ? sizeof(sockaddr_in6
) : sizeof(sockaddr_in
);
141 newConn
->local
.getSockAddr(lookup
, AF_UNSPEC
);
144 * Try NAT lookup for REDIRECT or DNAT targets. */
145 if ( getsockopt(newConn
->fd
,
146 newConn
->local
.isIPv6() ? IPPROTO_IPV6
: IPPROTO_IP
,
147 newConn
->local
.isIPv6() ? IP6T_SO_ORIGINAL_DST
: SO_ORIGINAL_DST
,
151 debugs(89, DBG_IMPORTANT
, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn
<< ": " << xstrerror());
152 lastReported_
= squid_curtime
;
154 debugs(89, 9, "address: " << newConn
);
157 newConn
->local
= lookup
;
158 debugs(89, 5, "address NAT: " << newConn
);
166 Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer
&newConn
, int silent
)
168 #if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
169 (PF_TRANSPARENT && defined(SO_BINDANY)) || \
170 (IPFW_TRANSPARENT && defined(IP_BINDANY))
172 /* Trust the user configured properly. If not no harm done.
173 * We will simply attempt a bind outgoing on our own IP.
175 newConn
->remote
.port(0); // allow random outgoing port to prevent address clashes
176 debugs(89, 5, HERE
<< "address TPROXY: " << newConn
);
184 Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
187 /* The getsockname() call performed already provided the TCP packet details.
188 * There is no way to identify whether they came from NAT or not.
189 * Trust the user configured properly.
191 debugs(89, 5, HERE
<< "address NAT: " << newConn
);
199 Ip::Intercept::IpfInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
201 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
203 struct natlookup natLookup
;
204 static int natfd
= -1;
207 // all fields must be set to 0
208 memset(&natLookup
, 0, sizeof(natLookup
));
209 // for NAT lookup set local and remote IP:port's
210 natLookup
.nl_inport
= htons(newConn
->local
.port());
211 newConn
->local
.getInAddr(natLookup
.nl_inip
);
212 natLookup
.nl_outport
= htons(newConn
->remote
.port());
213 newConn
->remote
.getInAddr(natLookup
.nl_outip
);
214 // ... and the TCP flag
215 natLookup
.nl_flags
= IPN_TCP
;
221 natfd
= open(IPNAT_NAME
, O_RDONLY
, 0);
223 natfd
= open(IPL_NAT
, O_RDONLY
, 0);
232 debugs(89, DBG_IMPORTANT
, "IPF (IPFilter) NAT open failed: " << xstrerror());
233 lastReported_
= squid_curtime
;
238 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
240 memset(&obj
, 0, sizeof(obj
));
241 obj
.ipfo_rev
= IPFILTER_VERSION
;
242 obj
.ipfo_size
= sizeof(natLookup
);
243 obj
.ipfo_ptr
= &natLookup
;
244 obj
.ipfo_type
= IPFOBJ_NATLOOKUP
;
246 x
= ioctl(natfd
, SIOCGNATL
, &obj
);
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.
255 static int siocgnatl_cmd
= SIOCGNATL
& 0xff;
256 if (63 == siocgnatl_cmd
) {
257 struct natlookup
*nlp
= &natLookup
;
258 x
= ioctl(natfd
, SIOCGNATL
, &nlp
);
260 x
= ioctl(natfd
, SIOCGNATL
, &natLookup
);
265 if (errno
!= ESRCH
) {
267 debugs(89, DBG_IMPORTANT
, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION
<< "): " << xstrerror());
268 lastReported_
= squid_curtime
;
275 debugs(89, 9, HERE
<< "address: " << newConn
);
278 newConn
->local
= natLookup
.nl_realip
;
279 newConn
->local
.port(ntohs(natLookup
.nl_realport
));
280 debugs(89, 5, HERE
<< "address NAT: " << newConn
);
284 #endif /* --enable-ipf-transparent */
289 Ip::Intercept::PfInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
291 #if PF_TRANSPARENT /* --enable-pf-transparent */
294 /* On recent PF versions the getsockname() call performed already provided
295 * the required TCP packet details.
296 * There is no way to identify whether they came from NAT or not.
298 * Trust the user configured properly.
300 debugs(89, 5, HERE
<< "address NAT divert-to: " << newConn
);
303 #else /* USE_NAT_DEVPF / --with-nat-devpf */
305 struct pfioc_natlook nl
;
306 static int pffd
= -1;
309 pffd
= open("/dev/pf", O_RDONLY
);
313 debugs(89, DBG_IMPORTANT
, HERE
<< "PF open failed: " << xstrerror());
314 lastReported_
= squid_curtime
;
319 memset(&nl
, 0, sizeof(struct pfioc_natlook
));
320 newConn
->remote
.getInAddr(nl
.saddr
.v4
);
321 nl
.sport
= htons(newConn
->remote
.port());
323 newConn
->local
.getInAddr(nl
.daddr
.v4
);
324 nl
.dport
= htons(newConn
->local
.port());
327 nl
.proto
= IPPROTO_TCP
;
328 nl
.direction
= PF_OUT
;
330 if (ioctl(pffd
, DIOCNATLOOK
, &nl
)) {
331 if (errno
!= ENOENT
) {
333 debugs(89, DBG_IMPORTANT
, HERE
<< "PF lookup failed: ioctl(DIOCNATLOOK)");
334 lastReported_
= squid_curtime
;
339 debugs(89, 9, HERE
<< "address: " << newConn
);
342 newConn
->local
= nl
.rdaddr
.v4
;
343 newConn
->local
.port(ntohs(nl
.rdport
));
344 debugs(89, 5, HERE
<< "address NAT: " << newConn
);
347 #endif /* --with-nat-devpf */
348 #endif /* --enable-pf-transparent */
353 Ip::Intercept::Lookup(const Comm::ConnectionPointer
&newConn
, const Comm::ConnectionPointer
&listenConn
)
355 /* --enable-linux-netfilter */
356 /* --enable-ipfw-transparent */
357 /* --enable-ipf-transparent */
358 /* --enable-pf-transparent */
359 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
362 // Crop interception errors down to one per minute.
363 int silent
= (squid_curtime
- lastReported_
> 60 ? 0 : 1);
365 // Show all interception errors.
369 debugs(89, 5, HERE
<< "address BEGIN: me/client= " << newConn
->local
<< ", destination/me= " << newConn
->remote
);
371 newConn
->flags
|= (listenConn
->flags
& (COMM_TRANSPARENT
|COMM_INTERCEPTION
));
373 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
374 if (transparentActive_
&& listenConn
->flags
&COMM_TRANSPARENT
) {
375 if (TproxyTransparent(newConn
, silent
)) return true;
378 if (interceptActive_
&& listenConn
->flags
&COMM_INTERCEPTION
) {
379 /* NAT methods that use sock-opts to return client address */
380 if (NetfilterInterception(newConn
, silent
)) return true;
381 if (IpfwInterception(newConn
, silent
)) return true;
383 /* NAT methods that use ioctl to return client address AND destination address */
384 if (PfInterception(newConn
, silent
)) return true;
385 if (IpfInterception(newConn
, silent
)) return true;
388 #else /* none of the transparent options configured */
389 debugs(89, DBG_IMPORTANT
, "WARNING: transparent proxying not supported");
396 Ip::Intercept::ProbeForTproxy(Ip::Address
&test
)
398 bool doneSuid
= false;
400 #if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
401 # define soLevel SOL_IP
402 # define soFlag IP_TRANSPARENT
404 #elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
405 # define soLevel SOL_SOCKET
406 # define soFlag SO_BINDANY
410 #elif defined(IP_BINDANY) // FreeBSD with IPFW
411 # define soLevel IPPROTO_IP
412 # define soFlag IP_BINDANY
418 #if defined(soLevel) && defined(soFlag)
420 debugs(3, 3, "Detect TPROXY support on port " << test
);
425 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
427 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
429 struct sockaddr_in6 tmp_ip6
;
430 Ip::Address tmp
= "::2";
432 tmp
.getSockAddr(tmp_ip6
);
434 if ( (tmp_sock
= socket(PF_INET6
, SOCK_STREAM
, IPPROTO_TCP
)) >= 0 &&
435 setsockopt(tmp_sock
, soLevel
, soFlag
, (char *)&tos
, sizeof(int)) == 0 &&
436 bind(tmp_sock
, (struct sockaddr
*)&tmp_ip6
, sizeof(struct sockaddr_in6
)) == 0 ) {
438 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
450 if ( test
.isIPv6() && !test
.setIPv4() ) {
451 debugs(3, DBG_CRITICAL
, "TPROXY lacks IPv6 support for " << test
);
457 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
459 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
461 struct sockaddr_in tmp_ip4
;
462 Ip::Address tmp
= "127.0.0.2";
464 tmp
.getSockAddr(tmp_ip4
);
466 if ( (tmp_sock
= socket(PF_INET
, SOCK_STREAM
, IPPROTO_TCP
)) >= 0 &&
467 setsockopt(tmp_sock
, soLevel
, soFlag
, (char *)&tos
, sizeof(int)) == 0 &&
468 bind(tmp_sock
, (struct sockaddr
*)&tmp_ip4
, sizeof(struct sockaddr_in
)) == 0 ) {
470 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
482 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");