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