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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors