]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ip/Intercept.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ip / Intercept.cc
CommitLineData
c8be6d7b 1/*
26ac0430 2 * DEBUG: section 89 NAT / IP Interception
c8be6d7b 3 * AUTHOR: Robert Collins
04f87469 4 * AUTHOR: Amos Jeffries
c8be6d7b 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.
26ac0430 22 *
c8be6d7b 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.
26ac0430 27 *
c8be6d7b 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 */
f7f3304a 33#include "squid.h"
40d34a62 34#include "comm/Connection.h"
b3166404 35#include "fde.h"
602d9612 36#include "ip/Intercept.h"
5c1be108 37#include "src/tools.h"
fc27cd70 38
c8be6d7b 39#if IPF_TRANSPARENT
34ec5c62 40
c8be6d7b 41#if HAVE_SYS_IOCTL_H
42#include <sys/ioctl.h>
43#endif
e9e7c285 44#if HAVE_NETINET_TCP_H
c8be6d7b 45#include <netinet/tcp.h>
e9e7c285 46#endif
47#if HAVE_NET_IF_H
c8be6d7b 48#include <net/if.h>
e9e7c285 49#endif
b0dfd10b 50#if HAVE_IPL_H
dbc5782a 51#include <ipl.h>
52#elif HAVE_NETINET_IPL_H
53#include <netinet/ipl.h>
54#endif
c8be6d7b 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>
63#endif
64#if HAVE_IP_FIL_H
65#include <ip_fil.h>
66#elif HAVE_NETINET_IP_FIL_H
67#include <netinet/ip_fil.h>
68#endif
69#if HAVE_IP_NAT_H
70#include <ip_nat.h>
71#elif HAVE_NETINET_IP_NAT_H
72#include <netinet/ip_nat.h>
73#endif
21d845b1
FC
74#if HAVE_ERRNO_H
75#include <errno.h>
76#endif
34ec5c62
AJ
77
78#endif /* IPF_TRANSPARENT required headers */
c8be6d7b 79
80#if PF_TRANSPARENT
c8be6d7b 81#include <sys/socket.h>
82#include <sys/ioctl.h>
83#include <sys/fcntl.h>
84#include <net/if.h>
85#include <netinet/in.h>
b0dfd10b 86#if HAVE_NET_PF_PFVAR_H
ec9909b0
AJ
87#include <net/pf/pfvar.h>
88#endif /* HAVE_NET_PF_PFVAR_H */
b0dfd10b 89#if HAVE_NET_PFVAR_H
c8be6d7b 90#include <net/pfvar.h>
ec9909b0 91#endif /* HAVE_NET_PFVAR_H */
34ec5c62 92#endif /* PF_TRANSPARENT required headers */
5afac208
AJ
93
94#if LINUX_NETFILTER
62c972a6 95#if HAVE_LIMITS_H
5afac208 96/* must be before including netfilter_ipv4.h */
62c972a6
FC
97#include <limits.h>
98#endif
ee80a919 99#include <linux/if.h>
c8be6d7b 100#include <linux/netfilter_ipv4.h>
ee80a919
AR
101#if HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
102/* 2013-07-01: Pablo the Netfilter maintainer is rejecting patches
103 * which will enable C++ compilers to build the Netfilter public headers.
104 * We can auto-detect its presence and attempt to use in case he ever
105 * changes his mind or things get cleaned up some other way.
106 * But until then are usually forced to hard-code the getsockopt() code
107 * for IPv6 NAT lookups.
108 */
109#include <linux/netfilter_ipv6/ip6_tables.h>
110#endif
111#if !defined(IP6T_SO_ORIGINAL_DST)
112#define IP6T_SO_ORIGINAL_DST 80 // stolen with prejudice from the above file.
113#endif
5afac208 114#endif /* LINUX_NETFILTER required headers */
c8be6d7b 115
0fc2952e 116// single global instance for access by other components.
b7ac5457 117Ip::Intercept Ip::Interceptor;
0fc2952e 118
04f87469 119void
b7ac5457 120Ip::Intercept::StopTransparency(const char *str)
26ac0430 121{
40d34a62 122 if (transparentActive_) {
788625af 123 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
40d34a62 124 transparentActive_ = 0;
788625af 125 }
04f87469
AJ
126}
127
128void
b7ac5457 129Ip::Intercept::StopInterception(const char *str)
26ac0430 130{
40d34a62 131 if (interceptActive_) {
788625af 132 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
40d34a62 133 interceptActive_ = 0;
788625af 134 }
04f87469 135}
0fc2952e 136
40d34a62
AJ
137bool
138Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
7b0a0d1f
AJ
139{
140#if LINUX_NETFILTER
ee80a919
AR
141 struct sockaddr_storage lookup;
142 socklen_t len = newConn->local.isIPv6() ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
143 newConn->local.getSockAddr(lookup, AF_UNSPEC);
7b0a0d1f
AJ
144
145 /** \par
146 * Try NAT lookup for REDIRECT or DNAT targets. */
ee80a919 147 if ( getsockopt(newConn->fd,
fa0b1a25
A
148 newConn->local.isIPv6() ? IPPROTO_IPV6 : IPPROTO_IP,
149 newConn->local.isIPv6() ? IP6T_SO_ORIGINAL_DST : SO_ORIGINAL_DST,
150 &lookup,
151 &len) != 0) {
26ac0430 152 if (!silent) {
ee80a919 153 debugs(89, DBG_IMPORTANT, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn << ": " << xstrerror());
40d34a62 154 lastReported_ = squid_curtime;
7b0a0d1f 155 }
ee80a919 156 debugs(89, 9, "address: " << newConn);
40d34a62 157 return false;
26ac0430 158 } else {
40d34a62 159 newConn->local = lookup;
ee80a919 160 debugs(89, 5, "address NAT: " << newConn);
40d34a62 161 return true;
7b0a0d1f 162 }
7b0a0d1f 163#endif
40d34a62 164 return false;
7b0a0d1f
AJ
165}
166
40d34a62 167bool
b2192042 168Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer &newConn, int silent)
7b0a0d1f 169{
b2192042
AJ
170#if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
171 (PF_TRANSPARENT && defined(SO_BINDANY)) || \
172 (IPFW_TRANSPARENT && defined(IP_BINDANY))
173
acaa7194
AJ
174 /* Trust the user configured properly. If not no harm done.
175 * We will simply attempt a bind outgoing on our own IP.
acaa7194 176 */
4dd643d5 177 newConn->remote.port(0); // allow random outgoing port to prevent address clashes
40d34a62
AJ
178 debugs(89, 5, HERE << "address TPROXY: " << newConn);
179 return true;
180#else
181 return false;
acaa7194 182#endif
7b0a0d1f
AJ
183}
184
40d34a62
AJ
185bool
186Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int silent)
d8b5bcbc
AJ
187{
188#if IPFW_TRANSPARENT
b2192042
AJ
189 /* The getsockname() call performed already provided the TCP packet details.
190 * There is no way to identify whether they came from NAT or not.
191 * Trust the user configured properly.
192 */
193 debugs(89, 5, HERE << "address NAT: " << newConn);
194 return true;
195#else
40d34a62 196 return false;
b2192042 197#endif
d8b5bcbc 198}
0fc2952e 199
40d34a62
AJ
200bool
201Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
3f38a55e 202{
f1e0717c 203#if IPF_TRANSPARENT /* --enable-ipf-transparent */
62e76326 204
c8be6d7b 205 struct natlookup natLookup;
206 static int natfd = -1;
c8be6d7b 207 int x;
3f38a55e 208
c74be4ce 209 // all fields must be set to 0
dcfb239f 210 memset(&natLookup, 0, sizeof(natLookup));
c74be4ce 211 // for NAT lookup set local and remote IP:port's
4dd643d5
AJ
212 natLookup.nl_inport = htons(newConn->local.port());
213 newConn->local.getInAddr(natLookup.nl_inip);
214 natLookup.nl_outport = htons(newConn->remote.port());
215 newConn->remote.getInAddr(natLookup.nl_outip);
c74be4ce 216 // ... and the TCP flag
c8be6d7b 217 natLookup.nl_flags = IPN_TCP;
62e76326 218
26ac0430 219 if (natfd < 0) {
62e76326 220 int save_errno;
221 enter_suid();
dbc5782a 222#ifdef IPNAT_NAME
dbc5782a 223 natfd = open(IPNAT_NAME, O_RDONLY, 0);
98a3bc99 224#else
62e76326 225 natfd = open(IPL_NAT, O_RDONLY, 0);
98a3bc99 226#endif
62e76326 227 save_errno = errno;
228 leave_suid();
229 errno = save_errno;
c8be6d7b 230 }
62e76326 231
26ac0430 232 if (natfd < 0) {
219f8edb 233 if (!silent) {
c74be4ce 234 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerror());
40d34a62
AJ
235 lastReported_ = squid_curtime;
236 return false;
d6026916 237 }
c8be6d7b 238 }
62e76326 239
dbc5782a 240#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
c74be4ce
AJ
241 struct ipfobj obj;
242 memset(&obj, 0, sizeof(obj));
243 obj.ipfo_rev = IPFILTER_VERSION;
244 obj.ipfo_size = sizeof(natLookup);
245 obj.ipfo_ptr = &natLookup;
246 obj.ipfo_type = IPFOBJ_NATLOOKUP;
247
dbc5782a 248 x = ioctl(natfd, SIOCGNATL, &obj);
dbc5782a 249#else
3f38a55e 250 /*
dbc5782a 251 * IP-Filter changed the type for SIOCGNATL between
252 * 3.3 and 3.4. It also changed the cmd value for
253 * SIOCGNATL, so at least we can detect it. We could
254 * put something in configure and use ifdefs here, but
255 * this seems simpler.
256 */
c74be4ce 257 static int siocgnatl_cmd = SIOCGNATL & 0xff;
26ac0430 258 if (63 == siocgnatl_cmd) {
62e76326 259 struct natlookup *nlp = &natLookup;
260 x = ioctl(natfd, SIOCGNATL, &nlp);
26ac0430 261 } else {
62e76326 262 x = ioctl(natfd, SIOCGNATL, &natLookup);
263 }
264
dbc5782a 265#endif
26ac0430 266 if (x < 0) {
62e76326 267 if (errno != ESRCH) {
219f8edb 268 if (!silent) {
c74be4ce 269 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerror());
40d34a62 270 lastReported_ = squid_curtime;
d6026916 271 }
272
62e76326 273 close(natfd);
274 natfd = -1;
275 }
276
40d34a62
AJ
277 debugs(89, 9, HERE << "address: " << newConn);
278 return false;
26ac0430 279 } else {
40d34a62 280 newConn->local = natLookup.nl_realip;
4dd643d5 281 newConn->local.port(ntohs(natLookup.nl_realport));
40d34a62
AJ
282 debugs(89, 5, HERE << "address NAT: " << newConn);
283 return true;
c8be6d7b 284 }
62e76326 285
219f8edb 286#endif /* --enable-ipf-transparent */
40d34a62 287 return false;
219f8edb 288}
f1e0717c 289
1125ea7b 290bool
b2192042 291Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
1125ea7b 292{
b2192042
AJ
293#if PF_TRANSPARENT /* --enable-pf-transparent */
294
295#if !USE_NAT_DEVPF
296 /* On recent PF versions the getsockname() call performed already provided
297 * the required TCP packet details.
298 * There is no way to identify whether they came from NAT or not.
299 *
300 * Trust the user configured properly.
1125ea7b 301 */
b2192042 302 debugs(89, 5, HERE << "address NAT divert-to: " << newConn);
1125ea7b 303 return true;
1125ea7b 304
b2192042 305#else /* USE_NAT_DEVPF / --with-nat-devpf */
62e76326 306
3f38a55e 307 struct pfioc_natlook nl;
308 static int pffd = -1;
62e76326 309
310 if (pffd < 0)
d14c6ef2 311 pffd = open("/dev/pf", O_RDONLY);
62e76326 312
26ac0430 313 if (pffd < 0) {
51f4d36b 314 if (!silent) {
e9172f79 315 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
40d34a62 316 lastReported_ = squid_curtime;
d6026916 317 }
40d34a62 318 return false;
c8be6d7b 319 }
62e76326 320
c8be6d7b 321 memset(&nl, 0, sizeof(struct pfioc_natlook));
4dd643d5
AJ
322 newConn->remote.getInAddr(nl.saddr.v4);
323 nl.sport = htons(newConn->remote.port());
cc192b50 324
4dd643d5
AJ
325 newConn->local.getInAddr(nl.daddr.v4);
326 nl.dport = htons(newConn->local.port());
cc192b50 327
c8be6d7b 328 nl.af = AF_INET;
329 nl.proto = IPPROTO_TCP;
330 nl.direction = PF_OUT;
62e76326 331
26ac0430 332 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
62e76326 333 if (errno != ENOENT) {
51f4d36b 334 if (!silent) {
e9172f79 335 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
40d34a62 336 lastReported_ = squid_curtime;
d6026916 337 }
62e76326 338 close(pffd);
339 pffd = -1;
340 }
40d34a62
AJ
341 debugs(89, 9, HERE << "address: " << newConn);
342 return false;
26ac0430 343 } else {
40d34a62 344 newConn->local = nl.rdaddr.v4;
4dd643d5 345 newConn->local.port(ntohs(nl.rdport));
40d34a62
AJ
346 debugs(89, 5, HERE << "address NAT: " << newConn);
347 return true;
3f38a55e 348 }
b2192042 349#endif /* --with-nat-devpf */
51f4d36b 350#endif /* --enable-pf-transparent */
40d34a62 351 return false;
51f4d36b
AJ
352}
353
40d34a62
AJ
354bool
355Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
51f4d36b 356{
af6a12ee
AJ
357 /* --enable-linux-netfilter */
358 /* --enable-ipfw-transparent */
359 /* --enable-ipf-transparent */
360 /* --enable-pf-transparent */
51f4d36b
AJ
361#if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
362
51f4d36b
AJ
363#if 0
364 // Crop interception errors down to one per minute.
40d34a62 365 int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
51f4d36b
AJ
366#else
367 // Show all interception errors.
368 int silent = 0;
369#endif
370
40d34a62
AJ
371 debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
372
373 newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
e9172f79 374
9bad3e09 375 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
40d34a62 376 if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
b2192042 377 if (TproxyTransparent(newConn, silent)) return true;
9bad3e09
AJ
378 }
379
40d34a62 380 if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
e9172f79 381 /* NAT methods that use sock-opts to return client address */
40d34a62
AJ
382 if (NetfilterInterception(newConn, silent)) return true;
383 if (IpfwInterception(newConn, silent)) return true;
e9172f79
AJ
384
385 /* NAT methods that use ioctl to return client address AND destination address */
40d34a62
AJ
386 if (PfInterception(newConn, silent)) return true;
387 if (IpfInterception(newConn, silent)) return true;
51f4d36b 388 }
f1e0717c 389
51f4d36b 390#else /* none of the transparent options configured */
e9172f79 391 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
3f38a55e 392#endif
393
40d34a62 394 return false;
f1e0717c 395}
34ec5c62 396
263f84f0 397bool
b7ac5457 398Ip::Intercept::ProbeForTproxy(Ip::Address &test)
263f84f0 399{
b2192042
AJ
400 bool doneSuid = false;
401
402#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
403# define soLevel SOL_IP
404# define soFlag IP_TRANSPARENT
405
406#elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
407# define soLevel SOL_SOCKET
408# define soFlag SO_BINDANY
409 enter_suid();
410 doneSuid = true;
411
412#elif defined(IP_BINDANY) // FreeBSD with IPFW
413# define soLevel IPPROTO_IP
414# define soFlag IP_BINDANY
415 enter_suid();
416 doneSuid = true;
417
418#endif
419
420#if defined(soLevel) && defined(soFlag)
421
1125ea7b 422 debugs(3, 3, "Detect TPROXY support on port " << test);
263f84f0
AJ
423
424 int tos = 1;
425 int tmp_sock = -1;
426
263f84f0 427 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
4dd643d5 428 if (test.isIPv6()) {
263f84f0
AJ
429 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
430
431 struct sockaddr_in6 tmp_ip6;
b7ac5457 432 Ip::Address tmp = "::2";
4dd643d5
AJ
433 tmp.port(0);
434 tmp.getSockAddr(tmp_ip6);
263f84f0
AJ
435
436 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
b2192042 437 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
f54f527e 438 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
263f84f0
AJ
439
440 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
fb5bffa3 441 close(tmp_sock);
b2192042
AJ
442 if (doneSuid)
443 leave_suid();
263f84f0
AJ
444 return true;
445 }
446 if (tmp_sock >= 0) {
fb5bffa3 447 close(tmp_sock);
263f84f0
AJ
448 tmp_sock = -1;
449 }
450 }
451
4dd643d5 452 if ( test.isIPv6() && !test.setIPv4() ) {
263f84f0 453 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
b2192042
AJ
454 if (doneSuid)
455 leave_suid();
263f84f0
AJ
456 return false;
457 }
263f84f0
AJ
458
459 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
4dd643d5 460 if (test.isIPv4()) {
263f84f0
AJ
461 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
462
463 struct sockaddr_in tmp_ip4;
b7ac5457 464 Ip::Address tmp = "127.0.0.2";
4dd643d5
AJ
465 tmp.port(0);
466 tmp.getSockAddr(tmp_ip4);
263f84f0
AJ
467
468 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
b2192042 469 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
f54f527e 470 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
263f84f0
AJ
471
472 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
fb5bffa3 473 close(tmp_sock);
b2192042
AJ
474 if (doneSuid)
475 leave_suid();
263f84f0
AJ
476 return true;
477 }
478 if (tmp_sock >= 0) {
fb5bffa3 479 close(tmp_sock);
263f84f0
AJ
480 }
481 }
482
1125ea7b
MM
483#else
484 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
485
263f84f0 486#endif
b2192042
AJ
487 if (doneSuid)
488 leave_suid();
263f84f0
AJ
489 return false;
490}