]> git.ipfire.org Git - thirdparty/squid.git/blame - src/ip/IpIntercept.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ip / IpIntercept.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 */
a74968c2 33#include "config.h"
b0dfd10b 34#include "ip/IpIntercept.h"
b3166404 35#include "fde.h"
fc27cd70 36
c8be6d7b 37#if IPF_TRANSPARENT
34ec5c62 38
c8be6d7b 39#if HAVE_SYS_IOCTL_H
40#include <sys/ioctl.h>
41#endif
e9e7c285 42#if HAVE_NETINET_TCP_H
c8be6d7b 43#include <netinet/tcp.h>
e9e7c285 44#endif
45#if HAVE_NET_IF_H
c8be6d7b 46#include <net/if.h>
e9e7c285 47#endif
b0dfd10b 48#if HAVE_IPL_H
dbc5782a 49#include <ipl.h>
50#elif HAVE_NETINET_IPL_H
51#include <netinet/ipl.h>
52#endif
c8be6d7b 53#if HAVE_IP_FIL_COMPAT_H
54#include <ip_fil_compat.h>
55#elif HAVE_NETINET_IP_FIL_COMPAT_H
56#include <netinet/ip_fil_compat.h>
57#elif HAVE_IP_COMPAT_H
58#include <ip_compat.h>
59#elif HAVE_NETINET_IP_COMPAT_H
60#include <netinet/ip_compat.h>
61#endif
62#if HAVE_IP_FIL_H
63#include <ip_fil.h>
64#elif HAVE_NETINET_IP_FIL_H
65#include <netinet/ip_fil.h>
66#endif
67#if HAVE_IP_NAT_H
68#include <ip_nat.h>
69#elif HAVE_NETINET_IP_NAT_H
70#include <netinet/ip_nat.h>
71#endif
34ec5c62
AJ
72
73#endif /* IPF_TRANSPARENT required headers */
c8be6d7b 74
75#if PF_TRANSPARENT
c8be6d7b 76#include <sys/socket.h>
77#include <sys/ioctl.h>
78#include <sys/fcntl.h>
79#include <net/if.h>
80#include <netinet/in.h>
b0dfd10b 81#if HAVE_NET_PF_PFVAR_H
ec9909b0
AJ
82#include <net/pf/pfvar.h>
83#endif /* HAVE_NET_PF_PFVAR_H */
b0dfd10b 84#if HAVE_NET_PFVAR_H
c8be6d7b 85#include <net/pfvar.h>
ec9909b0 86#endif /* HAVE_NET_PFVAR_H */
34ec5c62 87#endif /* PF_TRANSPARENT required headers */
c8be6d7b 88
89#if LINUX_NETFILTER
90#include <linux/netfilter_ipv4.h>
91#endif
92
34ec5c62 93#if LINUX_TPROXY2
b0dfd10b 94#if HAVE_LINUX_NETFILTER_IPV4_IP_TPROXY_H
34ec5c62
AJ
95#include <linux/netfilter_ipv4/ip_tproxy.h>
96#else
97#error " TPROXY v2 Header file missing: linux/netfilter_ipv4/ip_tproxy.h. Perhapse you meant to use TPROXY v4 ? "
98#endif
99#endif
100
0fc2952e
AJ
101
102// single global instance for access by other components.
85944c1c 103IpIntercept IpInterceptor;
0fc2952e 104
04f87469 105void
85944c1c 106IpIntercept::StopTransparency(const char *str)
26ac0430
AJ
107{
108 if (transparent_active) {
788625af
AJ
109 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
110 transparent_active = 0;
111 }
04f87469
AJ
112}
113
114void
85944c1c 115IpIntercept::StopInterception(const char *str)
26ac0430
AJ
116{
117 if (intercept_active) {
788625af
AJ
118 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
119 intercept_active = 0;
120 }
04f87469 121}
0fc2952e 122
7b0a0d1f 123int
23f6a720 124IpIntercept::NetfilterInterception(int fd, const IpAddress &me, IpAddress &dst, int silent)
7b0a0d1f
AJ
125{
126#if LINUX_NETFILTER
7b0a0d1f
AJ
127 struct addrinfo *lookup = NULL;
128
129 dst.GetAddrInfo(lookup,AF_INET);
130
131 /** \par
132 * Try NAT lookup for REDIRECT or DNAT targets. */
fa71b867 133 if ( getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST, lookup->ai_addr, &lookup->ai_addrlen) != 0) {
26ac0430 134 if (!silent) {
d6ae5979 135 debugs(89, DBG_IMPORTANT, HERE << " NF getsockopt(SO_ORIGINAL_DST) failed on FD " << fd << ": " << xstrerror());
7b0a0d1f
AJ
136 last_reported = squid_curtime;
137 }
26ac0430 138 } else {
7b0a0d1f
AJ
139 dst = *lookup;
140 }
141
142 dst.FreeAddrInfo(lookup);
143
26ac0430 144 if (me != dst) {
e9172f79 145 debugs(89, 5, HERE << "address NAT: me= " << me << ", dst= " << dst);
7b0a0d1f
AJ
146 return 0;
147 }
148
83707d8a 149 debugs(89, 9, HERE << "address: me= " << me << ", dst= " << dst);
7b0a0d1f
AJ
150#endif
151 return -1;
152}
153
154int
23f6a720 155IpIntercept::NetfilterTransparent(int fd, const IpAddress &me, IpAddress &client, int silent)
7b0a0d1f
AJ
156{
157#if LINUX_NETFILTER
7b0a0d1f 158
acaa7194
AJ
159 /* Trust the user configured properly. If not no harm done.
160 * We will simply attempt a bind outgoing on our own IP.
acaa7194 161 */
26ac0430 162 if (fd_table[fd].flags.transparent) {
482a5c64 163 client.SetPort(0); // allow random outgoing port to prevent address clashes
e9172f79 164 debugs(89, 5, HERE << "address TPROXY: me= " << me << ", client= " << client);
68d57d84
AJ
165 return 0;
166 }
219f8edb 167
e9172f79 168 debugs(89, 9, HERE << "address: me= " << me << ", client= " << client);
acaa7194 169#endif
68d57d84 170 return -1;
7b0a0d1f
AJ
171}
172
d8b5bcbc 173int
23f6a720 174IpIntercept::IpfwInterception(int fd, const IpAddress &me, IpAddress &dst, int silent)
d8b5bcbc
AJ
175{
176#if IPFW_TRANSPARENT
177 struct addrinfo *lookup = NULL;
178
179 dst.GetAddrInfo(lookup,AF_INET);
180
181 /** \par
182 * Try lookup for IPFW interception. */
26ac0430
AJ
183 if ( getsockname(fd, lookup->ai_addr, &lookup->ai_addrlen) != 0 ) {
184 if ( !silent ) {
d8b5bcbc
AJ
185 debugs(89, DBG_IMPORTANT, HERE << " IPFW getsockname(...) failed: " << xstrerror());
186 last_reported = squid_curtime;
187 }
26ac0430 188 } else {
d8b5bcbc
AJ
189 dst = *lookup;
190 }
191
192 dst.FreeAddrInfo(lookup);
193
26ac0430 194 if (me != dst) {
e9172f79 195 debugs(89, 5, HERE << "address NAT: me= " << me << ", dst= " << dst);
d8b5bcbc
AJ
196 return 0;
197 }
198
199 debugs(89, 9, HERE << "address: me= " << me << ", dst= " << dst);
200#endif
201 return -1;
202}
0fc2952e 203
3f38a55e 204int
e9172f79 205IpIntercept::IpfInterception(int fd, const IpAddress &me, IpAddress &client, IpAddress &dst, int silent)
3f38a55e 206{
f1e0717c 207#if IPF_TRANSPARENT /* --enable-ipf-transparent */
62e76326 208
dbc5782a 209#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
dbc5782a 210 struct ipfobj obj;
08a746ab 211#else
08a746ab 212 static int siocgnatl_cmd = SIOCGNATL & 0xff;
dbc5782a 213#endif
c8be6d7b 214 struct natlookup natLookup;
215 static int natfd = -1;
c8be6d7b 216 int x;
3f38a55e 217
dbc5782a 218#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
219
220 obj.ipfo_rev = IPFILTER_VERSION;
221 obj.ipfo_size = sizeof(natLookup);
222 obj.ipfo_ptr = &natLookup;
223 obj.ipfo_type = IPFOBJ_NATLOOKUP;
224 obj.ipfo_offset = 0;
225#endif
226
cc192b50 227 natLookup.nl_inport = htons(me.GetPort());
219f8edb 228 natLookup.nl_outport = htons(dst.GetPort());
cc192b50 229 me.GetInAddr(natLookup.nl_inip);
f24fa88d 230 dst.GetInAddr(natLookup.nl_outip);
c8be6d7b 231 natLookup.nl_flags = IPN_TCP;
62e76326 232
26ac0430 233 if (natfd < 0) {
62e76326 234 int save_errno;
235 enter_suid();
dbc5782a 236#ifdef IPNAT_NAME
dbc5782a 237 natfd = open(IPNAT_NAME, O_RDONLY, 0);
98a3bc99 238#else
62e76326 239 natfd = open(IPL_NAT, O_RDONLY, 0);
98a3bc99 240#endif
62e76326 241 save_errno = errno;
242 leave_suid();
243 errno = save_errno;
c8be6d7b 244 }
62e76326 245
26ac0430 246 if (natfd < 0) {
219f8edb 247 if (!silent) {
e9172f79 248 debugs(89, DBG_IMPORTANT, HERE << "NAT open failed: " << xstrerror());
d6026916 249 last_reported = squid_curtime;
250 return -1;
251 }
c8be6d7b 252 }
62e76326 253
dbc5782a 254#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
255 x = ioctl(natfd, SIOCGNATL, &obj);
dbc5782a 256#else
3f38a55e 257 /*
dbc5782a 258 * IP-Filter changed the type for SIOCGNATL between
259 * 3.3 and 3.4. It also changed the cmd value for
260 * SIOCGNATL, so at least we can detect it. We could
261 * put something in configure and use ifdefs here, but
262 * this seems simpler.
263 */
26ac0430 264 if (63 == siocgnatl_cmd) {
62e76326 265 struct natlookup *nlp = &natLookup;
266 x = ioctl(natfd, SIOCGNATL, &nlp);
26ac0430 267 } else {
62e76326 268 x = ioctl(natfd, SIOCGNATL, &natLookup);
269 }
270
dbc5782a 271#endif
26ac0430 272 if (x < 0) {
62e76326 273 if (errno != ESRCH) {
219f8edb 274 if (!silent) {
e9172f79 275 debugs(89, DBG_IMPORTANT, HERE << "NAT lookup failed: ioctl(SIOCGNATL)");
d6026916 276 last_reported = squid_curtime;
277 }
278
62e76326 279 close(natfd);
280 natfd = -1;
281 }
282
283 return -1;
26ac0430 284 } else {
e9172f79
AJ
285 if (client != natLookup.nl_realip) {
286 client = natLookup.nl_realip;
287 client.SetPort(ntohs(natLookup.nl_realport));
cc192b50 288 }
289 // else. we already copied it.
62e76326 290
e9172f79 291 debugs(89, 5, HERE << "address NAT: me= " << me << ", client= " << client << ", dst= " << dst);
62e76326 292 return 0;
c8be6d7b 293 }
62e76326 294
e9172f79 295 debugs(89, 9, HERE << "address: me= " << me << ", client= " << client << ", dst= " << dst);
219f8edb
AJ
296
297#endif /* --enable-ipf-transparent */
298 return -1;
299}
f1e0717c 300
219f8edb 301int
e9172f79 302IpIntercept::PfInterception(int fd, const IpAddress &me, IpAddress &client, IpAddress &dst, int silent)
219f8edb 303{
51f4d36b 304#if PF_TRANSPARENT /* --enable-pf-transparent */
62e76326 305
3f38a55e 306 struct pfioc_natlook nl;
307 static int pffd = -1;
62e76326 308
309 if (pffd < 0)
d14c6ef2 310 pffd = open("/dev/pf", O_RDONLY);
62e76326 311
26ac0430 312 if (pffd < 0) {
51f4d36b 313 if (!silent) {
e9172f79 314 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
d6026916 315 last_reported = squid_curtime;
316 }
62e76326 317 return -1;
c8be6d7b 318 }
62e76326 319
c8be6d7b 320 memset(&nl, 0, sizeof(struct pfioc_natlook));
51f4d36b
AJ
321 dst.GetInAddr(nl.saddr.v4);
322 nl.sport = htons(dst.GetPort());
cc192b50 323
e9172f79
AJ
324 me.GetInAddr(nl.daddr.v4);
325 nl.dport = htons(me.GetPort());
cc192b50 326
c8be6d7b 327 nl.af = AF_INET;
328 nl.proto = IPPROTO_TCP;
329 nl.direction = PF_OUT;
62e76326 330
26ac0430 331 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
62e76326 332 if (errno != ENOENT) {
51f4d36b 333 if (!silent) {
e9172f79 334 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
d6026916 335 last_reported = squid_curtime;
336 }
62e76326 337 close(pffd);
338 pffd = -1;
339 }
26ac0430 340 } else {
51f4d36b 341 int natted = (client != nl.rdaddr.v4);
482a5c64
AJ
342 client = nl.rdaddr.v4;
343 client.SetPort(ntohs(nl.rdport));
62e76326 344
e9172f79
AJ
345 if (natted) {
346 debugs(89, 5, HERE << "address NAT: me= " << me << ", client= " << client << ", dst= " << dst);
62e76326 347 return 0;
e9172f79 348 }
3f38a55e 349 }
62e76326 350
e9172f79 351 debugs(89, 9, HERE << "address: me= " << me << ", client= " << client << ", dst= " << dst);
f1e0717c 352
51f4d36b 353#endif /* --enable-pf-transparent */
cc192b50 354 return -1;
51f4d36b
AJ
355}
356
357
358int
359IpIntercept::NatLookup(int fd, const IpAddress &me, const IpAddress &peer, IpAddress &client, IpAddress &dst)
360{
af6a12ee
AJ
361 /* --enable-linux-netfilter */
362 /* --enable-ipfw-transparent */
363 /* --enable-ipf-transparent */
364 /* --enable-pf-transparent */
51f4d36b
AJ
365#if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
366
51f4d36b
AJ
367 client = me;
368 dst = peer;
369
51f4d36b
AJ
370#if 0
371 // Crop interception errors down to one per minute.
372 int silent = (squid_curtime - last_reported > 60 ? 0 : 1);
373#else
374 // Show all interception errors.
375 int silent = 0;
376#endif
377
e9172f79
AJ
378 debugs(89, 5, HERE << "address BEGIN: me= " << me << ", client= " << client <<
379 ", dst= " << dst << ", peer= " << peer);
380
9bad3e09
AJ
381 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
382 if (transparent_active) {
383 if ( NetfilterTransparent(fd, me, dst, silent) == 0) return 0;
384 }
385
b9420956 386 /* NAT is only available in IPv4 */
11298d0d
AJ
387 if ( !me.IsIPv4() ) return -1;
388 if ( !peer.IsIPv4() ) return -1;
389
51f4d36b 390 if (intercept_active) {
e9172f79 391 /* NAT methods that use sock-opts to return client address */
51f4d36b
AJ
392 if ( NetfilterInterception(fd, me, client, silent) == 0) return 0;
393 if ( IpfwInterception(fd, me, client, silent) == 0) return 0;
e9172f79
AJ
394
395 /* NAT methods that use ioctl to return client address AND destination address */
396 if ( PfInterception(fd, me, client, dst, silent) == 0) return 0;
397 if ( IpfInterception(fd, me, client, dst, silent) == 0) return 0;
51f4d36b 398 }
f1e0717c 399
51f4d36b 400#else /* none of the transparent options configured */
e9172f79 401 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
3f38a55e 402#endif
403
51f4d36b 404 return -1;
f1e0717c 405}
34ec5c62
AJ
406
407#if LINUX_TPROXY2
f8c3e85e 408int
565b233e 409IpIntercept::SetTproxy2OutgoingAddr(int fd, const IpAddress &src)
34ec5c62 410{
565b233e 411 IpAddress addr;
34ec5c62
AJ
412 struct in_tproxy itp;
413
414 src.GetInAddr(itp.v.addr.faddr);
415 itp.v.addr.fport = 0;
416
417 /* If these syscalls fail then we just fallback to connecting
418 * normally by simply ignoring the errors...
419 */
420 itp.op = TPROXY_ASSIGN;
421
422 addr = (struct in_addr)itp.v.addr.faddr;
423 addr.SetPort(itp.v.addr.fport);
424
425 if (setsockopt(fd, SOL_IP, IP_TPROXY, &itp, sizeof(itp)) == -1) {
426 debugs(20, 1, "tproxy ip=" << addr << " ERROR ASSIGN");
427 return -1;
428 } else {
429 itp.op = TPROXY_FLAGS;
430 itp.v.flags = ITP_CONNECT;
431
432 if (setsockopt(fd, SOL_IP, IP_TPROXY, &itp, sizeof(itp)) == -1) {
433 debugs(20, 1, "tproxy ip=" << addr << " ERROR CONNECT");
434 return -1;
435 }
436 }
437
438 return 0;
439}
440#endif
263f84f0
AJ
441
442bool
443IpIntercept::ProbeForTproxy(IpAddress &test)
444{
445 debugs(3, 3, "Detect TPROXY support on port " << test);
446#if LINUX_TPROXY2
447
448#if USE_IPV6
f54f527e
AJ
449 /* TPROXYv2 is not IPv6 capable. Force wildcard sockets to IPv4. Die on IPv6 IPs */
450 debugs(3, DBG_IMPORTANT, "Disabling IPv6 on port " << test << " (TPROXYv2 interception enabled)");
451 if ( test.IsIPv6() && !test.SetIPv4() ) {
452 debugs(3, DBG_CRITICAL, "IPv6 requires TPROXYv4 support. You only have TPROXYv2 for " << test );
453 return false;
454 }
263f84f0
AJ
455#endif /* USE_IPV6 */
456 return true;
457
458#else /* not LINUX_TPROXY2 */
459
460#if defined(IP_TRANSPARENT)
461
462 int tos = 1;
463 int tmp_sock = -1;
464
465#if USE_IPV6
466 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
467 if (test.IsIPv6()) {
468 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
469
470 struct sockaddr_in6 tmp_ip6;
471 IpAddress tmp = "::2";
472 tmp.SetPort(0);
473 tmp.GetSockAddr(tmp_ip6);
474
475 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
f54f527e
AJ
476 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
477 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
263f84f0
AJ
478
479 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
fb5bffa3 480 close(tmp_sock);
263f84f0
AJ
481 return true;
482 }
483 if (tmp_sock >= 0) {
fb5bffa3 484 close(tmp_sock);
263f84f0
AJ
485 tmp_sock = -1;
486 }
487 }
488
489 if ( test.IsIPv6() && !test.SetIPv4() ) {
490 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
491 return false;
492 }
493#endif
494
495 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
496 if (test.IsIPv4()) {
497 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
498
499 struct sockaddr_in tmp_ip4;
500 IpAddress tmp = "127.0.0.2";
501 tmp.SetPort(0);
502 tmp.GetSockAddr(tmp_ip4);
503
504 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
f54f527e
AJ
505 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
506 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
263f84f0
AJ
507
508 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
fb5bffa3 509 close(tmp_sock);
263f84f0
AJ
510 return true;
511 }
512 if (tmp_sock >= 0) {
fb5bffa3 513 close(tmp_sock);
263f84f0
AJ
514 }
515 }
516
517#else /* undefined IP_TRANSPARENT */
518 debugs(3, 3, "setsockopt(IP_TRANSPARENT) not supported on this platform. Disabling TPROXYv4.");
519#endif
520#endif /* LINUX_TPROXY2 */
521 return false;
522}