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