Intercept.cc
Go to the documentation of this file.
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 "compat/socket.h"
17 #include "compat/unistd.h"
18 #include "fde.h"
19 #include "ip/Intercept.h"
20 #include "ip/tools.h"
21 #include "src/tools.h"
22 
23 #include <cerrno>
24 
25 #if IPF_TRANSPARENT
26 
27 #if !defined(IPFILTER_VERSION)
28 #define IPFILTER_VERSION 5000004
29 #endif
30 
31 #if HAVE_SYS_PARAM_H
32 #include <sys/param.h>
33 #endif
34 #if HAVE_SYS_IOCCOM_H
35 #include <sys/ioccom.h>
36 #endif
37 #if HAVE_SYS_IOCTL_H
38 #include <sys/ioctl.h>
39 #endif
40 #if HAVE_NETINET_IP6_H
41 #include <netinet/ip6.h>
42 #endif
43 #if HAVE_NETINET_TCP_H
44 #include <netinet/tcp.h>
45 #endif
46 #if HAVE_NET_IF_H
47 #include <net/if.h>
48 #endif
49 #if HAVE_IPL_H
50 #include <ipl.h>
51 #elif HAVE_NETINET_IPL_H
52 #include <netinet/ipl.h>
53 #endif
54 #if USE_SOLARIS_IPFILTER_MINOR_T_HACK
55 #undef minor_t
56 #endif
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
76 
77 #endif /* IPF_TRANSPARENT required headers */
78 
79 #if PF_TRANSPARENT
80 #include <sys/ioctl.h>
81 #include <sys/fcntl.h>
82 #include <net/if.h>
83 #include <netinet/in.h>
84 #if HAVE_NET_PF_PFVAR_H
85 #include <net/pf/pfvar.h>
86 #endif /* HAVE_NET_PF_PFVAR_H */
87 #if HAVE_NET_PFVAR_H
88 #include <net/pfvar.h>
89 #endif /* HAVE_NET_PFVAR_H */
90 #endif /* PF_TRANSPARENT required headers */
91 
92 #if LINUX_NETFILTER
93 /* <climits> must be before including netfilter_ipv4.h */
94 #include <climits>
95 #include <linux/if.h>
96 #include <linux/netfilter_ipv4.h>
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)
108 #define IP6T_SO_ORIGINAL_DST 80 // stolen with prejudice from the above file.
109 #endif
110 #endif /* LINUX_NETFILTER required headers */
111 
112 // single global instance for access by other components.
114 
115 void
117 {
118  if (transparentActive_) {
119  debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
120  transparentActive_ = 0;
121  }
122 }
123 
124 bool
126 {
127 #if LINUX_NETFILTER
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);
131 
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) {
139  const auto xerrno = errno;
140  debugs(89, DBG_IMPORTANT, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn << ": " << xstrerr(xerrno));
141  return false;
142  } else {
143  newConn->local = lookup;
144  debugs(89, 5, "address NAT: " << newConn);
145  return true;
146  }
147 #else
148  (void)newConn;
149 #endif
150  return false;
151 }
152 
153 void
155 {
156  // --enable-linux-netfilter
157  // --enable-pf-transparent
158  // --enable-ipfw-transparent
159 #if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
160  (PF_TRANSPARENT && defined(SO_BINDANY)) || \
161  (IPFW_TRANSPARENT && defined(IP_BINDANY))
162  transparentActive_ = 1;
163 #else
164  throw TextException("requires TPROXY feature to be enabled by ./configure", Here());
165 #endif
166 }
167 
168 void
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;
177 #else
178  throw TextException("requires NAT Interception feature to be enabled by ./configure", Here());
179 #endif
180 }
181 
182 bool
184 {
185 #if IPFW_TRANSPARENT
186  return UseInterceptionAddressesLookedUpEarlier(__FUNCTION__, newConn);
187 #else
188  (void)newConn;
189  return false;
190 #endif
191 }
192 
196 bool
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 
212 bool
214 {
215 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
216 
217  struct natlookup natLookup;
218  static int natfd = -1;
219  int x;
220 
221  // all fields must be set to 0
222  memset(&natLookup, 0, sizeof(natLookup));
223  // for NAT lookup set local and remote IP:port's
224  if (newConn->remote.isIPv6()) {
225 #if HAVE_STRUCT_NATLOOKUP_NL_INIPADDR_IN6
226  natLookup.nl_v = 6;
227  newConn->local.getInAddr(natLookup.nl_inipaddr.in6);
228  newConn->remote.getInAddr(natLookup.nl_outipaddr.in6);
229  }
230  else {
231  natLookup.nl_v = 4;
232  newConn->local.getInAddr(natLookup.nl_inipaddr.in4);
233  newConn->remote.getInAddr(natLookup.nl_outipaddr.in4);
234  }
235 #else /* HAVE_STRUCT_NATLOOKUP_NL_INIPADDR_IN6 */
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);
244 #endif /* HAVE_STRUCT_NATLOOKUP_NL_INIPADDR_IN6 */
245  natLookup.nl_inport = htons(newConn->local.port());
246  natLookup.nl_outport = htons(newConn->remote.port());
247  // ... and the TCP flag
248  natLookup.nl_flags = IPN_TCP;
249 
250  if (natfd < 0) {
251  int save_errno;
252  enter_suid();
253 #ifdef IPNAT_NAME
254  natfd = xopen(IPNAT_NAME, O_RDONLY, 0);
255 #else
256  natfd = xopen(IPL_NAT, O_RDONLY, 0);
257 #endif
258  save_errno = errno;
259  leave_suid();
260  errno = save_errno;
261  }
262 
263  if (natfd < 0) {
264  const auto xerrno = errno;
265  debugs(89, DBG_IMPORTANT, "ERROR: IPF (IPFilter) NAT open failed: " << xstrerr(xerrno));
266  return false;
267  }
268 
269 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
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 
277  x = ioctl(natfd, SIOCGNATL, &obj);
278 #else
279  /*
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  */
286  static int siocgnatl_cmd = SIOCGNATL & 0xff;
287  if (63 == siocgnatl_cmd) {
288  struct natlookup *nlp = &natLookup;
289  x = ioctl(natfd, SIOCGNATL, &nlp);
290  } else {
291  x = ioctl(natfd, SIOCGNATL, &natLookup);
292  }
293 
294 #endif /* defined(IPFILTER_VERSION) ... */
295  if (x < 0) {
296  const auto xerrno = errno;
297  if (xerrno != ESRCH) {
298  debugs(89, DBG_IMPORTANT, "ERROR: IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerr(xerrno));
299  xclose(natfd);
300  natfd = -1;
301  }
302 
303  debugs(89, 9, "address: " << newConn);
304  return false;
305  } else {
306 #if HAVE_STRUCT_NATLOOKUP_NL_REALIPADDR_IN6
307  if (newConn->remote.isIPv6())
308  newConn->local = natLookup.nl_realipaddr.in6;
309  else
310  newConn->local = natLookup.nl_realipaddr.in4;
311 #else
312  newConn->local = natLookup.nl_realip;
313 #endif
314  newConn->local.port(ntohs(natLookup.nl_realport));
315  debugs(89, 5, "address NAT: " << newConn);
316  return true;
317  }
318 
319 #else
320  (void)newConn;
321 #endif /* --enable-ipf-transparent */
322  return false;
323 }
324 
325 bool
327 {
328 #if PF_TRANSPARENT /* --enable-pf-transparent */
329 
330 #if !USE_NAT_DEVPF
331  return UseInterceptionAddressesLookedUpEarlier("recent PF version", newConn);
332 
333 #else /* USE_NAT_DEVPF / --with-nat-devpf */
334 
335  struct pfioc_natlook nl;
336  static int pffd = -1;
337 
338  if (pffd < 0)
339  pffd = xopen("/dev/pf", O_RDONLY);
340 
341  if (pffd < 0) {
342  const auto xerrno = errno;
343  debugs(89, DBG_IMPORTANT, "ERROR: PF open failed: " << xstrerr(xerrno));
344  return false;
345  }
346 
347  memset(&nl, 0, sizeof(struct pfioc_natlook));
348 
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());
360  nl.dport = htons(newConn->local.port());
361 
362  nl.proto = IPPROTO_TCP;
363  nl.direction = PF_OUT;
364 
365  if (ioctl(pffd, DIOCNATLOOK, &nl)) {
366  const auto xerrno = errno;
367  if (xerrno != ENOENT) {
368  debugs(89, DBG_IMPORTANT, "ERROR: PF lookup failed: ioctl(DIOCNATLOOK): " << xstrerr(xerrno));
369  xclose(pffd);
370  pffd = -1;
371  }
372  debugs(89, 9, "address: " << newConn);
373  return false;
374  } else {
375  if (newConn->remote.isIPv6())
376  newConn->local = nl.rdaddr.v6;
377  else
378  newConn->local = nl.rdaddr.v4;
379  newConn->local.port(ntohs(nl.rdport));
380  debugs(89, 5, "address NAT: " << newConn);
381  return true;
382  }
383 #endif /* --with-nat-devpf */
384 #else
385  (void)newConn;
386 #endif /* --enable-pf-transparent */
387  return false;
388 }
389 
390 bool
392 {
393  debugs(89, 5, "address BEGIN: me/client= " << aConn.local << ", destination/me= " << aConn.remote);
394  assert(interceptActive_);
395 
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
399 }
400 
401 bool
403 {
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 
426  debugs(3, 3, "Detect TPROXY support on port " << test);
427 
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 
435  int tos = 1;
436  int tmp_sock = -1;
437 
438  /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
439  if (test.isIPv6()) {
440  debugs(3, 3, "...Probing for IPv6 TPROXY support.");
441 
442  struct sockaddr_in6 tmp_ip6;
443  Ip::Address tmp = "::2";
444  tmp.port(0);
445  tmp.getSockAddr(tmp_ip6);
446 
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 ) {
450 
451  debugs(3, 3, "IPv6 TPROXY support detected. Using.");
452  xclose(tmp_sock);
453  if (doneSuid)
454  leave_suid();
455  return true;
456  }
457  if (tmp_sock >= 0) {
458  xclose(tmp_sock);
459  tmp_sock = -1;
460  }
461  }
462 
463  if ( test.isIPv6() && !test.setIPv4() ) {
464  debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
465  if (doneSuid)
466  leave_suid();
467  return false;
468  }
469 
470  /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
471  if (test.isIPv4()) {
472  debugs(3, 3, "...Probing for IPv4 TPROXY support.");
473 
474  struct sockaddr_in tmp_ip4;
475  Ip::Address tmp = "127.0.0.2";
476  tmp.port(0);
477  tmp.getSockAddr(tmp_ip4);
478 
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 ) {
482 
483  debugs(3, 3, "IPv4 TPROXY support detected. Using.");
484  xclose(tmp_sock);
485  if (doneSuid)
486  leave_suid();
487  return true;
488  }
489  if (tmp_sock >= 0) {
490  xclose(tmp_sock);
491  }
492  }
493 
494 #else
495  debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY on port " << test);
496 
497 #endif
498  if (doneSuid)
499  leave_suid();
500  return false;
501 }
502 
const char * xstrerr(int error)
Definition: xstrerror.cc:83
#define Here()
source code location of the caller
Definition: Here.h:15
#define DBG_CRITICAL
Definition: Stream.h:37
void StopTransparency(const char *str)
Definition: Intercept.cc:116
void StartTransparency()
Definition: Intercept.cc:154
bool NetfilterInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:125
bool getInAddr(struct in_addr &) const
Definition: Address.cc:1040
bool isIPv4() const
Definition: Address.cc:178
int socklen_t
Definition: types.h:137
bool IpfwInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:183
bool UseInterceptionAddressesLookedUpEarlier(const char *, const Comm::ConnectionPointer &)
Definition: Intercept.cc:197
void leave_suid(void)
Definition: tools.cc:560
int transparentActive_
Definition: Intercept.h:122
unsigned short port() const
Definition: Address.cc:798
bool isIPv6() const
Definition: Address.cc:184
Ip::Address local
Definition: Connection.h:149
int xsetsockopt(int socketFd, int level, int option, const void *value, socklen_t valueLength)
POSIX setsockopt(2) equivalent.
Definition: socket.h:122
Ip::Address remote
Definition: Connection.h:152
#define assert(EX)
Definition: assert.h:17
bool setIPv4()
Definition: Address.cc:244
bool IpfInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:213
int xgetsockopt(int socketFd, int level, int optionName, void *optionValue, socklen_t *optionLength)
POSIX getsockopt(2) equivalent.
Definition: socket.h:92
void StartInterception()
Definition: Intercept.cc:169
bool ProbeForTproxy(Address &test)
Definition: Intercept.cc:402
Intercept Interceptor
Definition: Intercept.h:135
int xsocket(int domain, int type, int protocol)
POSIX socket(2) equivalent.
Definition: socket.h:128
an std::runtime_error with thrower location info
Definition: TextException.h:20
bool LookupNat(const Comm::Connection &)
Definition: Intercept.cc:391
#define IP6T_SO_ORIGINAL_DST
Definition: Intercept.cc:108
int xbind(int socketFd, const struct sockaddr *sa, socklen_t saLength)
POSIX bind(2) equivalent.
Definition: socket.h:68
void enter_suid(void)
Definition: tools.cc:624
#define DBG_IMPORTANT
Definition: Stream.h:38
int xopen(const char *filename, int oflag, int pmode=0)
POSIX open(2) equivalent.
Definition: unistd.h:55
bool PfInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:326
int xclose(int fd)
POSIX close(2) equivalent.
Definition: unistd.h:43
int EnableIpv6
Whether IPv6 is supported and type of support.
Definition: tools.h:25
void getSockAddr(struct sockaddr_storage &addr, const int family) const
Definition: Address.cc:944
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192

 

Introduction

Documentation

Support

Miscellaneous