]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/ip/Intercept.cc
2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 89 NAT / IP Interception */
11 // Enable hack to workaround Solaris 10 IPFilter breakage
12 #define BUILDING_SQUID_IP_INTERCEPT_CC 1
15 #include "comm/Connection.h"
17 #include "ip/Intercept.h"
18 #include "src/tools.h"
24 #if !defined(IPFILTER_VERSION)
25 #define IPFILTER_VERSION 5000004
29 #include <sys/param.h>
32 #include <sys/ioccom.h>
35 #include <sys/ioctl.h>
37 #if HAVE_NETINET_IP6_H
38 #include <netinet/ip6.h>
40 #if HAVE_NETINET_TCP_H
41 #include <netinet/tcp.h>
48 #elif HAVE_NETINET_IPL_H
49 #include <netinet/ipl.h>
51 #if USE_SOLARIS_IPFILTER_MINOR_T_HACK
54 #if HAVE_IP_FIL_COMPAT_H
55 #include <ip_fil_compat.h>
56 #elif HAVE_NETINET_IP_FIL_COMPAT_H
57 #include <netinet/ip_fil_compat.h>
58 #elif HAVE_IP_COMPAT_H
59 #include <ip_compat.h>
60 #elif HAVE_NETINET_IP_COMPAT_H
61 #include <netinet/ip_compat.h>
65 #elif HAVE_NETINET_IP_FIL_H
66 #include <netinet/ip_fil.h>
70 #elif HAVE_NETINET_IP_NAT_H
71 #include <netinet/ip_nat.h>
74 #endif /* IPF_TRANSPARENT required headers */
77 #include <sys/socket.h>
78 #include <sys/ioctl.h>
79 #include <sys/fcntl.h>
81 #include <netinet/in.h>
82 #if HAVE_NET_PF_PFVAR_H
83 #include <net/pf/pfvar.h>
84 #endif /* HAVE_NET_PF_PFVAR_H */
86 #include <net/pfvar.h>
87 #endif /* HAVE_NET_PFVAR_H */
88 #endif /* PF_TRANSPARENT required headers */
91 /* <climits> must be before including netfilter_ipv4.h */
94 #include <linux/netfilter_ipv4.h>
95 #if HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
96 /* 2013-07-01: Pablo the Netfilter maintainer is rejecting patches
97 * which will enable C++ compilers to build the Netfilter public headers.
98 * We can auto-detect its presence and attempt to use in case he ever
99 * changes his mind or things get cleaned up some other way.
100 * But until then are usually forced to hard-code the getsockopt() code
101 * for IPv6 NAT lookups.
103 #include <linux/netfilter_ipv6/ip6_tables.h>
105 #if !defined(IP6T_SO_ORIGINAL_DST)
106 #define IP6T_SO_ORIGINAL_DST 80 // stolen with prejudice from the above file.
108 #endif /* LINUX_NETFILTER required headers */
110 // single global instance for access by other components.
111 Ip::Intercept
Ip::Interceptor
;
114 Ip::Intercept::StopTransparency(const char *str
)
116 if (transparentActive_
) {
117 debugs(89, DBG_IMPORTANT
, "Stopping full transparency: " << str
);
118 transparentActive_
= 0;
123 Ip::Intercept::StopInterception(const char *str
)
125 if (interceptActive_
) {
126 debugs(89, DBG_IMPORTANT
, "Stopping IP interception: " << str
);
127 interceptActive_
= 0;
132 Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
135 struct sockaddr_storage lookup
;
136 socklen_t len
= newConn
->local
.isIPv6() ? sizeof(sockaddr_in6
) : sizeof(sockaddr_in
);
137 newConn
->local
.getSockAddr(lookup
, AF_UNSPEC
);
140 * Try NAT lookup for REDIRECT or DNAT targets. */
141 if ( getsockopt(newConn
->fd
,
142 newConn
->local
.isIPv6() ? IPPROTO_IPV6
: IPPROTO_IP
,
143 newConn
->local
.isIPv6() ? IP6T_SO_ORIGINAL_DST
: SO_ORIGINAL_DST
,
148 debugs(89, DBG_IMPORTANT
, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn
<< ": " << xstrerr(xerrno
));
149 lastReported_
= squid_curtime
;
151 debugs(89, 9, "address: " << newConn
);
154 newConn
->local
= lookup
;
155 debugs(89, 5, "address NAT: " << newConn
);
163 Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer
&newConn
, int)
165 #if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
166 (PF_TRANSPARENT && defined(SO_BINDANY)) || \
167 (IPFW_TRANSPARENT && defined(IP_BINDANY))
169 /* Trust the user configured properly. If not no harm done.
170 * We will simply attempt a bind outgoing on our own IP.
172 debugs(89, 5, HERE
<< "address TPROXY: " << newConn
);
180 Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer
&newConn
, int)
183 /* The getsockname() call performed already provided the TCP packet details.
184 * There is no way to identify whether they came from NAT or not.
185 * Trust the user configured properly.
187 debugs(89, 5, HERE
<< "address NAT: " << newConn
);
195 Ip::Intercept::IpfInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
197 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
199 struct natlookup natLookup
;
200 static int natfd
= -1;
203 // all fields must be set to 0
204 memset(&natLookup
, 0, sizeof(natLookup
));
205 // for NAT lookup set local and remote IP:port's
206 if (newConn
->remote
.isIPv6()) {
207 #if HAVE_NATLOOKUP_NL_INIPADDR_IN6
209 newConn
->local
.getInAddr(natLookup
.nl_inipaddr
.in6
);
210 newConn
->remote
.getInAddr(natLookup
.nl_outipaddr
.in6
);
214 newConn
->local
.getInAddr(natLookup
.nl_inipaddr
.in4
);
215 newConn
->remote
.getInAddr(natLookup
.nl_outipaddr
.in4
);
218 // warn once every 10 at critical level, then push down a level each repeated event
219 static int warningLevel
= DBG_CRITICAL
;
220 debugs(89, warningLevel
, "Your IPF (IPFilter) NAT does not support IPv6. Please upgrade it.");
221 warningLevel
= (warningLevel
+ 1) % 10;
224 newConn
->local
.getInAddr(natLookup
.nl_inip
);
225 newConn
->remote
.getInAddr(natLookup
.nl_outip
);
227 natLookup
.nl_inport
= htons(newConn
->local
.port());
228 natLookup
.nl_outport
= htons(newConn
->remote
.port());
229 // ... and the TCP flag
230 natLookup
.nl_flags
= IPN_TCP
;
236 natfd
= open(IPNAT_NAME
, O_RDONLY
, 0);
238 natfd
= open(IPL_NAT
, O_RDONLY
, 0);
248 debugs(89, DBG_IMPORTANT
, "IPF (IPFilter) NAT open failed: " << xstrerr(xerrno
));
249 lastReported_
= squid_curtime
;
254 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
256 memset(&obj
, 0, sizeof(obj
));
257 obj
.ipfo_rev
= IPFILTER_VERSION
;
258 obj
.ipfo_size
= sizeof(natLookup
);
259 obj
.ipfo_ptr
= &natLookup
;
260 obj
.ipfo_type
= IPFOBJ_NATLOOKUP
;
262 x
= ioctl(natfd
, SIOCGNATL
, &obj
);
265 * IP-Filter changed the type for SIOCGNATL between
266 * 3.3 and 3.4. It also changed the cmd value for
267 * SIOCGNATL, so at least we can detect it. We could
268 * put something in configure and use ifdefs here, but
269 * this seems simpler.
271 static int siocgnatl_cmd
= SIOCGNATL
& 0xff;
272 if (63 == siocgnatl_cmd
) {
273 struct natlookup
*nlp
= &natLookup
;
274 x
= ioctl(natfd
, SIOCGNATL
, &nlp
);
276 x
= ioctl(natfd
, SIOCGNATL
, &natLookup
);
282 if (xerrno
!= ESRCH
) {
284 debugs(89, DBG_IMPORTANT
, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION
<< "): " << xstrerr(xerrno
));
285 lastReported_
= squid_curtime
;
292 debugs(89, 9, HERE
<< "address: " << newConn
);
295 #if HAVE_NATLOOKUP_NL_REALIPADDR_IN6
296 if (newConn
->remote
.isIPv6())
297 newConn
->local
= natLookup
.nl_realipaddr
.in6
;
299 newConn
->local
= natLookup
.nl_realipaddr
.in4
;
301 newConn
->local
= natLookup
.nl_realip
;
303 newConn
->local
.port(ntohs(natLookup
.nl_realport
));
304 debugs(89, 5, HERE
<< "address NAT: " << newConn
);
308 #endif /* --enable-ipf-transparent */
313 Ip::Intercept::PfInterception(const Comm::ConnectionPointer
&newConn
, int silent
)
315 #if PF_TRANSPARENT /* --enable-pf-transparent */
318 /* On recent PF versions the getsockname() call performed already provided
319 * the required TCP packet details.
320 * There is no way to identify whether they came from NAT or not.
322 * Trust the user configured properly.
324 debugs(89, 5, HERE
<< "address NAT divert-to: " << newConn
);
327 #else /* USE_NAT_DEVPF / --with-nat-devpf */
329 struct pfioc_natlook nl
;
330 static int pffd
= -1;
333 pffd
= open("/dev/pf", O_RDONLY
);
338 debugs(89, DBG_IMPORTANT
, MYNAME
<< "PF open failed: " << xstrerr(xerrno
));
339 lastReported_
= squid_curtime
;
344 memset(&nl
, 0, sizeof(struct pfioc_natlook
));
346 if (newConn
->remote
.isIPv6()) {
347 newConn
->remote
.getInAddr(nl
.saddr
.v6
);
348 newConn
->local
.getInAddr(nl
.daddr
.v6
);
351 newConn
->remote
.getInAddr(nl
.saddr
.v4
);
352 newConn
->local
.getInAddr(nl
.daddr
.v4
);
356 nl
.sport
= htons(newConn
->remote
.port());
357 nl
.dport
= htons(newConn
->local
.port());
359 nl
.proto
= IPPROTO_TCP
;
360 nl
.direction
= PF_OUT
;
362 if (ioctl(pffd
, DIOCNATLOOK
, &nl
)) {
364 if (xerrno
!= ENOENT
) {
366 debugs(89, DBG_IMPORTANT
, HERE
<< "PF lookup failed: ioctl(DIOCNATLOOK): " << xstrerr(xerrno
));
367 lastReported_
= squid_curtime
;
372 debugs(89, 9, HERE
<< "address: " << newConn
);
375 if (newConn
->remote
.isIPv6())
376 newConn
->local
= nl
.rdaddr
.v6
;
378 newConn
->local
= nl
.rdaddr
.v4
;
379 newConn
->local
.port(ntohs(nl
.rdport
));
380 debugs(89, 5, HERE
<< "address NAT: " << newConn
);
383 #endif /* --with-nat-devpf */
384 #endif /* --enable-pf-transparent */
389 Ip::Intercept::Lookup(const Comm::ConnectionPointer
&newConn
, const Comm::ConnectionPointer
&listenConn
)
391 /* --enable-linux-netfilter */
392 /* --enable-ipfw-transparent */
393 /* --enable-ipf-transparent */
394 /* --enable-pf-transparent */
395 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
398 // Crop interception errors down to one per minute.
399 int silent
= (squid_curtime
- lastReported_
> 60 ? 0 : 1);
401 // Show all interception errors.
405 debugs(89, 5, HERE
<< "address BEGIN: me/client= " << newConn
->local
<< ", destination/me= " << newConn
->remote
);
407 newConn
->flags
|= (listenConn
->flags
& (COMM_TRANSPARENT
|COMM_INTERCEPTION
));
409 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
410 if (transparentActive_
&& listenConn
->flags
&COMM_TRANSPARENT
) {
411 if (TproxyTransparent(newConn
, silent
)) return true;
414 if (interceptActive_
&& listenConn
->flags
&COMM_INTERCEPTION
) {
415 /* NAT methods that use sock-opts to return client address */
416 if (NetfilterInterception(newConn
, silent
)) return true;
417 if (IpfwInterception(newConn
, silent
)) return true;
419 /* NAT methods that use ioctl to return client address AND destination address */
420 if (PfInterception(newConn
, silent
)) return true;
421 if (IpfInterception(newConn
, silent
)) return true;
424 #else /* none of the transparent options configured */
425 debugs(89, DBG_IMPORTANT
, "WARNING: transparent proxying not supported");
432 Ip::Intercept::ProbeForTproxy(Ip::Address
&test
)
434 bool doneSuid
= false;
436 #if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
437 # define soLevel SOL_IP
438 # define soFlag IP_TRANSPARENT
440 #elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
441 # define soLevel SOL_SOCKET
442 # define soFlag SO_BINDANY
446 #elif defined(IP_BINDANY) // FreeBSD with IPFW
447 # define soLevel IPPROTO_IP
448 # define soFlag IP_BINDANY
454 #if defined(soLevel) && defined(soFlag)
456 debugs(3, 3, "Detect TPROXY support on port " << test
);
461 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
463 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
465 struct sockaddr_in6 tmp_ip6
;
466 Ip::Address tmp
= "::2";
468 tmp
.getSockAddr(tmp_ip6
);
470 if ( (tmp_sock
= socket(PF_INET6
, SOCK_STREAM
, IPPROTO_TCP
)) >= 0 &&
471 setsockopt(tmp_sock
, soLevel
, soFlag
, (char *)&tos
, sizeof(int)) == 0 &&
472 bind(tmp_sock
, (struct sockaddr
*)&tmp_ip6
, sizeof(struct sockaddr_in6
)) == 0 ) {
474 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
486 if ( test
.isIPv6() && !test
.setIPv4() ) {
487 debugs(3, DBG_CRITICAL
, "TPROXY lacks IPv6 support for " << test
);
493 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
495 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
497 struct sockaddr_in tmp_ip4
;
498 Ip::Address tmp
= "127.0.0.2";
500 tmp
.getSockAddr(tmp_ip4
);
502 if ( (tmp_sock
= socket(PF_INET
, SOCK_STREAM
, IPPROTO_TCP
)) >= 0 &&
503 setsockopt(tmp_sock
, soLevel
, soFlag
, (char *)&tos
, sizeof(int)) == 0 &&
504 bind(tmp_sock
, (struct sockaddr
*)&tmp_ip4
, sizeof(struct sockaddr_in
)) == 0 ) {
506 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
518 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");