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