]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ip/Intercept.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ip / Intercept.cc
CommitLineData
c8be6d7b 1/*
bde978a6 2 * Copyright (C) 1996-2015 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) {
ee80a919 144 debugs(89, DBG_IMPORTANT, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn << ": " << xstrerror());
40d34a62 145 lastReported_ = squid_curtime;
7b0a0d1f 146 }
ee80a919 147 debugs(89, 9, "address: " << newConn);
40d34a62 148 return false;
26ac0430 149 } else {
40d34a62 150 newConn->local = lookup;
ee80a919 151 debugs(89, 5, "address NAT: " << newConn);
40d34a62 152 return true;
7b0a0d1f 153 }
7b0a0d1f 154#endif
40d34a62 155 return false;
7b0a0d1f
AJ
156}
157
40d34a62 158bool
ced8def3 159Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer &newConn, int)
7b0a0d1f 160{
b2192042
AJ
161#if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
162 (PF_TRANSPARENT && defined(SO_BINDANY)) || \
163 (IPFW_TRANSPARENT && defined(IP_BINDANY))
164
acaa7194
AJ
165 /* Trust the user configured properly. If not no harm done.
166 * We will simply attempt a bind outgoing on our own IP.
acaa7194 167 */
4dd643d5 168 newConn->remote.port(0); // allow random outgoing port to prevent address clashes
40d34a62
AJ
169 debugs(89, 5, HERE << "address TPROXY: " << newConn);
170 return true;
171#else
172 return false;
acaa7194 173#endif
7b0a0d1f
AJ
174}
175
40d34a62 176bool
ced8def3 177Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int)
d8b5bcbc
AJ
178{
179#if IPFW_TRANSPARENT
b2192042
AJ
180 /* The getsockname() call performed already provided the TCP packet details.
181 * There is no way to identify whether they came from NAT or not.
182 * Trust the user configured properly.
183 */
184 debugs(89, 5, HERE << "address NAT: " << newConn);
185 return true;
186#else
40d34a62 187 return false;
b2192042 188#endif
d8b5bcbc 189}
0fc2952e 190
40d34a62
AJ
191bool
192Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
3f38a55e 193{
f1e0717c 194#if IPF_TRANSPARENT /* --enable-ipf-transparent */
62e76326 195
c8be6d7b 196 struct natlookup natLookup;
197 static int natfd = -1;
c8be6d7b 198 int x;
3f38a55e 199
c74be4ce 200 // all fields must be set to 0
dcfb239f 201 memset(&natLookup, 0, sizeof(natLookup));
c74be4ce 202 // for NAT lookup set local and remote IP:port's
4dd643d5
AJ
203 natLookup.nl_inport = htons(newConn->local.port());
204 newConn->local.getInAddr(natLookup.nl_inip);
205 natLookup.nl_outport = htons(newConn->remote.port());
206 newConn->remote.getInAddr(natLookup.nl_outip);
c74be4ce 207 // ... and the TCP flag
c8be6d7b 208 natLookup.nl_flags = IPN_TCP;
62e76326 209
26ac0430 210 if (natfd < 0) {
62e76326 211 int save_errno;
212 enter_suid();
dbc5782a 213#ifdef IPNAT_NAME
dbc5782a 214 natfd = open(IPNAT_NAME, O_RDONLY, 0);
98a3bc99 215#else
62e76326 216 natfd = open(IPL_NAT, O_RDONLY, 0);
98a3bc99 217#endif
62e76326 218 save_errno = errno;
219 leave_suid();
220 errno = save_errno;
c8be6d7b 221 }
62e76326 222
26ac0430 223 if (natfd < 0) {
219f8edb 224 if (!silent) {
c74be4ce 225 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerror());
40d34a62
AJ
226 lastReported_ = squid_curtime;
227 return false;
d6026916 228 }
c8be6d7b 229 }
62e76326 230
dbc5782a 231#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
c74be4ce
AJ
232 struct ipfobj obj;
233 memset(&obj, 0, sizeof(obj));
234 obj.ipfo_rev = IPFILTER_VERSION;
235 obj.ipfo_size = sizeof(natLookup);
236 obj.ipfo_ptr = &natLookup;
237 obj.ipfo_type = IPFOBJ_NATLOOKUP;
238
dbc5782a 239 x = ioctl(natfd, SIOCGNATL, &obj);
dbc5782a 240#else
3f38a55e 241 /*
dbc5782a 242 * IP-Filter changed the type for SIOCGNATL between
243 * 3.3 and 3.4. It also changed the cmd value for
244 * SIOCGNATL, so at least we can detect it. We could
245 * put something in configure and use ifdefs here, but
246 * this seems simpler.
247 */
c74be4ce 248 static int siocgnatl_cmd = SIOCGNATL & 0xff;
26ac0430 249 if (63 == siocgnatl_cmd) {
62e76326 250 struct natlookup *nlp = &natLookup;
251 x = ioctl(natfd, SIOCGNATL, &nlp);
26ac0430 252 } else {
62e76326 253 x = ioctl(natfd, SIOCGNATL, &natLookup);
254 }
255
dbc5782a 256#endif
26ac0430 257 if (x < 0) {
62e76326 258 if (errno != ESRCH) {
219f8edb 259 if (!silent) {
c74be4ce 260 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerror());
40d34a62 261 lastReported_ = squid_curtime;
d6026916 262 }
263
62e76326 264 close(natfd);
265 natfd = -1;
266 }
267
40d34a62
AJ
268 debugs(89, 9, HERE << "address: " << newConn);
269 return false;
26ac0430 270 } else {
40d34a62 271 newConn->local = natLookup.nl_realip;
4dd643d5 272 newConn->local.port(ntohs(natLookup.nl_realport));
40d34a62
AJ
273 debugs(89, 5, HERE << "address NAT: " << newConn);
274 return true;
c8be6d7b 275 }
62e76326 276
219f8edb 277#endif /* --enable-ipf-transparent */
40d34a62 278 return false;
219f8edb 279}
f1e0717c 280
1125ea7b 281bool
b2192042 282Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
1125ea7b 283{
b2192042
AJ
284#if PF_TRANSPARENT /* --enable-pf-transparent */
285
286#if !USE_NAT_DEVPF
287 /* On recent PF versions the getsockname() call performed already provided
288 * the required TCP packet details.
289 * There is no way to identify whether they came from NAT or not.
290 *
291 * Trust the user configured properly.
1125ea7b 292 */
b2192042 293 debugs(89, 5, HERE << "address NAT divert-to: " << newConn);
1125ea7b 294 return true;
1125ea7b 295
b2192042 296#else /* USE_NAT_DEVPF / --with-nat-devpf */
62e76326 297
3f38a55e 298 struct pfioc_natlook nl;
299 static int pffd = -1;
62e76326 300
301 if (pffd < 0)
d14c6ef2 302 pffd = open("/dev/pf", O_RDONLY);
62e76326 303
26ac0430 304 if (pffd < 0) {
51f4d36b 305 if (!silent) {
e9172f79 306 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
40d34a62 307 lastReported_ = squid_curtime;
d6026916 308 }
40d34a62 309 return false;
c8be6d7b 310 }
62e76326 311
c8be6d7b 312 memset(&nl, 0, sizeof(struct pfioc_natlook));
4dd643d5
AJ
313 newConn->remote.getInAddr(nl.saddr.v4);
314 nl.sport = htons(newConn->remote.port());
cc192b50 315
4dd643d5
AJ
316 newConn->local.getInAddr(nl.daddr.v4);
317 nl.dport = htons(newConn->local.port());
cc192b50 318
c8be6d7b 319 nl.af = AF_INET;
320 nl.proto = IPPROTO_TCP;
321 nl.direction = PF_OUT;
62e76326 322
26ac0430 323 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
62e76326 324 if (errno != ENOENT) {
51f4d36b 325 if (!silent) {
e9172f79 326 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
40d34a62 327 lastReported_ = squid_curtime;
d6026916 328 }
62e76326 329 close(pffd);
330 pffd = -1;
331 }
40d34a62
AJ
332 debugs(89, 9, HERE << "address: " << newConn);
333 return false;
26ac0430 334 } else {
40d34a62 335 newConn->local = nl.rdaddr.v4;
4dd643d5 336 newConn->local.port(ntohs(nl.rdport));
40d34a62
AJ
337 debugs(89, 5, HERE << "address NAT: " << newConn);
338 return true;
3f38a55e 339 }
b2192042 340#endif /* --with-nat-devpf */
51f4d36b 341#endif /* --enable-pf-transparent */
40d34a62 342 return false;
51f4d36b
AJ
343}
344
40d34a62
AJ
345bool
346Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
51f4d36b 347{
af6a12ee
AJ
348 /* --enable-linux-netfilter */
349 /* --enable-ipfw-transparent */
350 /* --enable-ipf-transparent */
351 /* --enable-pf-transparent */
51f4d36b
AJ
352#if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
353
51f4d36b
AJ
354#if 0
355 // Crop interception errors down to one per minute.
40d34a62 356 int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
51f4d36b
AJ
357#else
358 // Show all interception errors.
359 int silent = 0;
360#endif
361
40d34a62
AJ
362 debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
363
364 newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
e9172f79 365
9bad3e09 366 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
40d34a62 367 if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
b2192042 368 if (TproxyTransparent(newConn, silent)) return true;
9bad3e09
AJ
369 }
370
40d34a62 371 if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
e9172f79 372 /* NAT methods that use sock-opts to return client address */
40d34a62
AJ
373 if (NetfilterInterception(newConn, silent)) return true;
374 if (IpfwInterception(newConn, silent)) return true;
e9172f79
AJ
375
376 /* NAT methods that use ioctl to return client address AND destination address */
40d34a62
AJ
377 if (PfInterception(newConn, silent)) return true;
378 if (IpfInterception(newConn, silent)) return true;
51f4d36b 379 }
f1e0717c 380
51f4d36b 381#else /* none of the transparent options configured */
e9172f79 382 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
3f38a55e 383#endif
384
40d34a62 385 return false;
f1e0717c 386}
34ec5c62 387
263f84f0 388bool
b7ac5457 389Ip::Intercept::ProbeForTproxy(Ip::Address &test)
263f84f0 390{
b2192042
AJ
391 bool doneSuid = false;
392
393#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
394# define soLevel SOL_IP
395# define soFlag IP_TRANSPARENT
396
397#elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
398# define soLevel SOL_SOCKET
399# define soFlag SO_BINDANY
400 enter_suid();
401 doneSuid = true;
402
403#elif defined(IP_BINDANY) // FreeBSD with IPFW
404# define soLevel IPPROTO_IP
405# define soFlag IP_BINDANY
406 enter_suid();
407 doneSuid = true;
408
409#endif
410
411#if defined(soLevel) && defined(soFlag)
412
1125ea7b 413 debugs(3, 3, "Detect TPROXY support on port " << test);
263f84f0
AJ
414
415 int tos = 1;
416 int tmp_sock = -1;
417
263f84f0 418 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
4dd643d5 419 if (test.isIPv6()) {
263f84f0
AJ
420 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
421
422 struct sockaddr_in6 tmp_ip6;
b7ac5457 423 Ip::Address tmp = "::2";
4dd643d5
AJ
424 tmp.port(0);
425 tmp.getSockAddr(tmp_ip6);
263f84f0
AJ
426
427 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
b2192042 428 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
f54f527e 429 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
263f84f0
AJ
430
431 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
fb5bffa3 432 close(tmp_sock);
b2192042
AJ
433 if (doneSuid)
434 leave_suid();
263f84f0
AJ
435 return true;
436 }
437 if (tmp_sock >= 0) {
fb5bffa3 438 close(tmp_sock);
263f84f0
AJ
439 tmp_sock = -1;
440 }
441 }
442
4dd643d5 443 if ( test.isIPv6() && !test.setIPv4() ) {
263f84f0 444 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
b2192042
AJ
445 if (doneSuid)
446 leave_suid();
263f84f0
AJ
447 return false;
448 }
263f84f0
AJ
449
450 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
4dd643d5 451 if (test.isIPv4()) {
263f84f0
AJ
452 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
453
454 struct sockaddr_in tmp_ip4;
b7ac5457 455 Ip::Address tmp = "127.0.0.2";
4dd643d5
AJ
456 tmp.port(0);
457 tmp.getSockAddr(tmp_ip4);
263f84f0
AJ
458
459 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
b2192042 460 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
f54f527e 461 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
263f84f0
AJ
462
463 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
fb5bffa3 464 close(tmp_sock);
b2192042
AJ
465 if (doneSuid)
466 leave_suid();
263f84f0
AJ
467 return true;
468 }
469 if (tmp_sock >= 0) {
fb5bffa3 470 close(tmp_sock);
263f84f0
AJ
471 }
472 }
473
1125ea7b
MM
474#else
475 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
476
263f84f0 477#endif
b2192042
AJ
478 if (doneSuid)
479 leave_suid();
263f84f0
AJ
480 return false;
481}
f53969cc 482