Intercept.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2017 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 
113 void
115 {
116  if (transparentActive_) {
117  debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
118  transparentActive_ = 0;
119  }
120 }
121 
122 void
124 {
125  if (interceptActive_) {
126  debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
127  interceptActive_ = 0;
128  }
129 }
130 
131 bool
133 {
134 #if LINUX_NETFILTER
135  struct sockaddr_storage lookup;
136  socklen_t len = newConn->local.isIPv6() ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
137  newConn->local.getSockAddr(lookup, AF_UNSPEC);
138 
141  if ( getsockopt(newConn->fd,
142  newConn->local.isIPv6() ? IPPROTO_IPV6 : IPPROTO_IP,
143  newConn->local.isIPv6() ? IP6T_SO_ORIGINAL_DST : SO_ORIGINAL_DST,
144  &lookup,
145  &len) != 0) {
146  if (!silent) {
147  int xerrno = errno;
148  debugs(89, DBG_IMPORTANT, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn << ": " << xstrerr(xerrno));
149  lastReported_ = squid_curtime;
150  }
151  debugs(89, 9, "address: " << newConn);
152  return false;
153  } else {
154  newConn->local = lookup;
155  debugs(89, 5, "address NAT: " << newConn);
156  return true;
157  }
158 #endif
159  return false;
160 }
161 
162 bool
164 {
165 #if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
166  (PF_TRANSPARENT && defined(SO_BINDANY)) || \
167  (IPFW_TRANSPARENT && defined(IP_BINDANY))
168 
169  /* Trust the user configured properly. If not no harm done.
170  * We will simply attempt a bind outgoing on our own IP.
171  */
172  newConn->remote.port(0); // allow random outgoing port to prevent address clashes
173  debugs(89, 5, HERE << "address TPROXY: " << newConn);
174  return true;
175 #else
176  return false;
177 #endif
178 }
179 
180 bool
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, HERE << "address NAT: " << newConn);
189  return true;
190 #else
191  return false;
192 #endif
193 }
194 
195 bool
197 {
198 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
199 
200  struct natlookup natLookup;
201  static int natfd = -1;
202  int x;
203 
204  // all fields must be set to 0
205  memset(&natLookup, 0, sizeof(natLookup));
206  // for NAT lookup set local and remote IP:port's
207  if (newConn->remote.isIPv6()) {
208 #if IPFILTER_VERSION < 5000003
209  // warn once every 10 at critical level, then push down a level each repeated event
210  static int warningLevel = DBG_CRITICAL;
211  debugs(89, warningLevel, "IPF (IPFilter v4) NAT does not support IPv6. Please upgrade to IPFilter v5.1");
212  warningLevel = (warningLevel + 1) % 10;
213  return false;
214  }
215  newConn->local.getInAddr(natLookup.nl_inip);
216  newConn->remote.getInAddr(natLookup.nl_outip);
217 #else
218  natLookup.nl_v = 6;
219  newConn->local.getInAddr(natLookup.nl_inipaddr.in6);
220  newConn->remote.getInAddr(natLookup.nl_outipaddr.in6);
221  }
222  else {
223  natLookup.nl_v = 4;
224  newConn->local.getInAddr(natLookup.nl_inipaddr.in4);
225  newConn->remote.getInAddr(natLookup.nl_outipaddr.in4);
226  }
227 #endif
228  natLookup.nl_inport = htons(newConn->local.port());
229  natLookup.nl_outport = htons(newConn->remote.port());
230  // ... and the TCP flag
231  natLookup.nl_flags = IPN_TCP;
232 
233  if (natfd < 0) {
234  int save_errno;
235  enter_suid();
236 #ifdef IPNAT_NAME
237  natfd = open(IPNAT_NAME, O_RDONLY, 0);
238 #else
239  natfd = open(IPL_NAT, O_RDONLY, 0);
240 #endif
241  save_errno = errno;
242  leave_suid();
243  errno = save_errno;
244  }
245 
246  if (natfd < 0) {
247  if (!silent) {
248  int xerrno = errno;
249  debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerr(xerrno));
250  lastReported_ = squid_curtime;
251  return false;
252  }
253  }
254 
255 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
256  struct ipfobj obj;
257  memset(&obj, 0, sizeof(obj));
258  obj.ipfo_rev = IPFILTER_VERSION;
259  obj.ipfo_size = sizeof(natLookup);
260  obj.ipfo_ptr = &natLookup;
261  obj.ipfo_type = IPFOBJ_NATLOOKUP;
262 
263  x = ioctl(natfd, SIOCGNATL, &obj);
264 #else
265  /*
266  * IP-Filter changed the type for SIOCGNATL between
267  * 3.3 and 3.4. It also changed the cmd value for
268  * SIOCGNATL, so at least we can detect it. We could
269  * put something in configure and use ifdefs here, but
270  * this seems simpler.
271  */
272  static int siocgnatl_cmd = SIOCGNATL & 0xff;
273  if (63 == siocgnatl_cmd) {
274  struct natlookup *nlp = &natLookup;
275  x = ioctl(natfd, SIOCGNATL, &nlp);
276  } else {
277  x = ioctl(natfd, SIOCGNATL, &natLookup);
278  }
279 
280 #endif
281  if (x < 0) {
282  int xerrno = errno;
283  if (xerrno != ESRCH) {
284  if (!silent) {
285  debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerr(xerrno));
286  lastReported_ = squid_curtime;
287  }
288 
289  close(natfd);
290  natfd = -1;
291  }
292 
293  debugs(89, 9, HERE << "address: " << newConn);
294  return false;
295  } else {
296 #if IPFILTER_VERSION < 5000003
297  newConn->local = natLookup.nl_realip;
298 #else
299  if (newConn->remote.isIPv6())
300  newConn->local = natLookup.nl_realipaddr.in6;
301  else
302  newConn->local = natLookup.nl_realipaddr.in4;
303 #endif
304  newConn->local.port(ntohs(natLookup.nl_realport));
305  debugs(89, 5, HERE << "address NAT: " << newConn);
306  return true;
307  }
308 
309 #endif /* --enable-ipf-transparent */
310  return false;
311 }
312 
313 bool
315 {
316 #if PF_TRANSPARENT /* --enable-pf-transparent */
317 
318 #if !USE_NAT_DEVPF
319  /* On recent PF versions the getsockname() call performed already provided
320  * the required TCP packet details.
321  * There is no way to identify whether they came from NAT or not.
322  *
323  * Trust the user configured properly.
324  */
325  debugs(89, 5, HERE << "address NAT divert-to: " << newConn);
326  return true;
327 
328 #else /* USE_NAT_DEVPF / --with-nat-devpf */
329 
330  struct pfioc_natlook nl;
331  static int pffd = -1;
332 
333  if (pffd < 0)
334  pffd = open("/dev/pf", O_RDONLY);
335 
336  if (pffd < 0) {
337  if (!silent) {
338  int xerrno = errno;
339  debugs(89, DBG_IMPORTANT, MYNAME << "PF open failed: " << xstrerr(xerrno));
340  lastReported_ = squid_curtime;
341  }
342  return false;
343  }
344 
345  memset(&nl, 0, sizeof(struct pfioc_natlook));
346 
347  if (newConn->remote.isIPv6()) {
348  newConn->remote.getInAddr(nl.saddr.v6);
349  newConn->local.getInAddr(nl.daddr.v6);
350  nl.af = AF_INET6;
351  } else {
352  newConn->remote.getInAddr(nl.saddr.v4);
353  newConn->local.getInAddr(nl.daddr.v4);
354  nl.af = AF_INET;
355  }
356 
357  nl.sport = htons(newConn->remote.port());
358  nl.dport = htons(newConn->local.port());
359 
360  nl.proto = IPPROTO_TCP;
361  nl.direction = PF_OUT;
362 
363  if (ioctl(pffd, DIOCNATLOOK, &nl)) {
364  int xerrno = errno;
365  if (xerrno != ENOENT) {
366  if (!silent) {
367  debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK): " << xstrerr(xerrno));
368  lastReported_ = squid_curtime;
369  }
370  close(pffd);
371  pffd = -1;
372  }
373  debugs(89, 9, HERE << "address: " << newConn);
374  return false;
375  } else {
376  if (newConn->remote.isIPv6())
377  newConn->local = nl.rdaddr.v6;
378  else
379  newConn->local = nl.rdaddr.v4;
380  newConn->local.port(ntohs(nl.rdport));
381  debugs(89, 5, HERE << "address NAT: " << newConn);
382  return true;
383  }
384 #endif /* --with-nat-devpf */
385 #endif /* --enable-pf-transparent */
386  return false;
387 }
388 
389 bool
391 {
392  /* --enable-linux-netfilter */
393  /* --enable-ipfw-transparent */
394  /* --enable-ipf-transparent */
395  /* --enable-pf-transparent */
396 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
397 
398 #if 0
399  // Crop interception errors down to one per minute.
400  int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
401 #else
402  // Show all interception errors.
403  int silent = 0;
404 #endif
405 
406  debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
407 
408  newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
409 
410  /* NP: try TPROXY first, its much quieter than NAT when non-matching */
411  if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
412  if (TproxyTransparent(newConn, silent)) return true;
413  }
414 
415  if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
416  /* NAT methods that use sock-opts to return client address */
417  if (NetfilterInterception(newConn, silent)) return true;
418  if (IpfwInterception(newConn, silent)) return true;
419 
420  /* NAT methods that use ioctl to return client address AND destination address */
421  if (PfInterception(newConn, silent)) return true;
422  if (IpfInterception(newConn, silent)) return true;
423  }
424 
425 #else /* none of the transparent options configured */
426  debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
427 #endif
428 
429  return false;
430 }
431 
432 bool
434 {
435  bool doneSuid = false;
436 
437 #if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
438 # define soLevel SOL_IP
439 # define soFlag IP_TRANSPARENT
440 
441 #elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
442 # define soLevel SOL_SOCKET
443 # define soFlag SO_BINDANY
444  enter_suid();
445  doneSuid = true;
446 
447 #elif defined(IP_BINDANY) // FreeBSD with IPFW
448 # define soLevel IPPROTO_IP
449 # define soFlag IP_BINDANY
450  enter_suid();
451  doneSuid = true;
452 
453 #endif
454 
455 #if defined(soLevel) && defined(soFlag)
456 
457  debugs(3, 3, "Detect TPROXY support on port " << test);
458 
459  int tos = 1;
460  int tmp_sock = -1;
461 
462  /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
463  if (test.isIPv6()) {
464  debugs(3, 3, "...Probing for IPv6 TPROXY support.");
465 
466  struct sockaddr_in6 tmp_ip6;
467  Ip::Address tmp = "::2";
468  tmp.port(0);
469  tmp.getSockAddr(tmp_ip6);
470 
471  if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
472  setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
473  bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
474 
475  debugs(3, 3, "IPv6 TPROXY support detected. Using.");
476  close(tmp_sock);
477  if (doneSuid)
478  leave_suid();
479  return true;
480  }
481  if (tmp_sock >= 0) {
482  close(tmp_sock);
483  tmp_sock = -1;
484  }
485  }
486 
487  if ( test.isIPv6() && !test.setIPv4() ) {
488  debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
489  if (doneSuid)
490  leave_suid();
491  return false;
492  }
493 
494  /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
495  if (test.isIPv4()) {
496  debugs(3, 3, "...Probing for IPv4 TPROXY support.");
497 
498  struct sockaddr_in tmp_ip4;
499  Ip::Address tmp = "127.0.0.2";
500  tmp.port(0);
501  tmp.getSockAddr(tmp_ip4);
502 
503  if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
504  setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
505  bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
506 
507  debugs(3, 3, "IPv4 TPROXY support detected. Using.");
508  close(tmp_sock);
509  if (doneSuid)
510  leave_suid();
511  return true;
512  }
513  if (tmp_sock >= 0) {
514  close(tmp_sock);
515  }
516  }
517 
518 #else
519  debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
520 
521 #endif
522  if (doneSuid)
523  leave_suid();
524  return false;
525 }
526 
bool setIPv4()
Definition: Address.cc:217
bool Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
Definition: Intercept.cc:390
int socklen_t
Definition: types.h:158
bool ProbeForTproxy(Address &test)
Definition: Intercept.cc:433
bool IpfwInterception(const Comm::ConnectionPointer &newConn, int silent)
Definition: Intercept.cc:181
#define COMM_INTERCEPTION
Definition: Connection.h:49
#define DBG_CRITICAL
Definition: Debug.h:44
bool PfInterception(const Comm::ConnectionPointer &newConn, int silent)
Definition: Intercept.cc:314
time_t squid_curtime
Definition: stub_time.cc:17
bool isIPv6() const
Definition: Address.cc:157
const char * xstrerr(int error)
Definition: xstrerror.cc:83
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
#define DBG_IMPORTANT
Definition: Debug.h:45
bool TproxyTransparent(const Comm::ConnectionPointer &newConn, int silent)
Definition: Intercept.cc:163
void leave_suid(void)
Definition: tools.cc:504
bool NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
Definition: Intercept.cc:132
bool isIPv4() const
Definition: Address.cc:151
int unsigned int const char *desc STUB void int len
Definition: stub_fd.cc:20
void StopInterception(const char *str)
Definition: Intercept.cc:123
std::ostream & HERE(std::ostream &s)
Definition: Debug.h:147
void getSockAddr(struct sockaddr_storage &addr, const int family) const
Definition: Address.cc:942
unsigned short port() const
Definition: Address.cc:786
Ip::Address local
Definition: Connection.h:135
#define MYNAME
Definition: Debug.h:160
Ip::Address remote
Definition: Connection.h:138
#define COMM_TRANSPARENT
Definition: Connection.h:48
int transparentActive_
Definition: Intercept.h:140
void enter_suid(void)
Definition: tools.cc:575
void StopTransparency(const char *str)
Definition: Intercept.cc:114
bool IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
Definition: Intercept.cc:196
bool getInAddr(struct in_addr &) const
Definition: Address.cc:1038
#define IP6T_SO_ORIGINAL_DST
Definition: Intercept.cc:106
Intercept Interceptor
Definition: Intercept.h:154

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors