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 "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.
113 
114 void
116 {
117  if (transparentActive_) {
118  debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
119  transparentActive_ = 0;
120  }
121 }
122 
123 bool
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 
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 
152 void
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 
167 void
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 
181 bool
183 {
184 #if IPFW_TRANSPARENT
185  return UseInterceptionAddressesLookedUpEarlier(__FUNCTION__, newConn);
186 #else
187  (void)newConn;
188  return false;
189 #endif
190 }
191 
195 bool
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 
211 bool
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 
324 bool
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 
389 bool
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 
400 bool
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 
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:115
void StartTransparency()
Definition: Intercept.cc:153
bool NetfilterInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:124
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:182
bool UseInterceptionAddressesLookedUpEarlier(const char *, const Comm::ConnectionPointer &)
Definition: Intercept.cc:196
void leave_suid(void)
Definition: tools.cc:559
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:146
Ip::Address remote
Definition: Connection.h:149
#define assert(EX)
Definition: assert.h:17
bool setIPv4()
Definition: Address.cc:244
bool IpfInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:212
void StartInterception()
Definition: Intercept.cc:168
bool ProbeForTproxy(Address &test)
Definition: Intercept.cc:401
Intercept Interceptor
Definition: Intercept.h:135
an std::runtime_error with thrower location info
Definition: TextException.h:20
bool LookupNat(const Comm::Connection &)
Definition: Intercept.cc:390
#define IP6T_SO_ORIGINAL_DST
Definition: Intercept.cc:107
void enter_suid(void)
Definition: tools.cc:623
#define DBG_IMPORTANT
Definition: Stream.h:38
bool PfInterception(const Comm::ConnectionPointer &newConn)
Definition: Intercept.cc:325
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