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