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