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