]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ip/Intercept.cc
Cleanup: un-wrap C++ header includes
[thirdparty/squid.git] / src / ip / Intercept.cc
1 /*
2 * DEBUG: section 89 NAT / IP Interception
3 * AUTHOR: Robert Collins
4 * AUTHOR: Amos Jeffries
5 *
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
8 *
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 *
32 */
33 #include "squid.h"
34 #include "comm/Connection.h"
35 #include "fde.h"
36 #include "ip/Intercept.h"
37 #include "src/tools.h"
38
39 #if IPF_TRANSPARENT
40
41 #if HAVE_SYS_IOCTL_H
42 #include <sys/ioctl.h>
43 #endif
44 #if HAVE_NETINET_TCP_H
45 #include <netinet/tcp.h>
46 #endif
47 #if HAVE_NET_IF_H
48 #include <net/if.h>
49 #endif
50 #if HAVE_IPL_H
51 #include <ipl.h>
52 #elif HAVE_NETINET_IPL_H
53 #include <netinet/ipl.h>
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 #if HAVE_ERRNO_H
75 #include <errno.h>
76 #endif
77
78 #endif /* IPF_TRANSPARENT required headers */
79
80 #if PF_TRANSPARENT
81 #include <sys/socket.h>
82 #include <sys/ioctl.h>
83 #include <sys/fcntl.h>
84 #include <net/if.h>
85 #include <netinet/in.h>
86 #if HAVE_NET_PF_PFVAR_H
87 #include <net/pf/pfvar.h>
88 #endif /* HAVE_NET_PF_PFVAR_H */
89 #if HAVE_NET_PFVAR_H
90 #include <net/pfvar.h>
91 #endif /* HAVE_NET_PFVAR_H */
92 #endif /* PF_TRANSPARENT required headers */
93
94 #if LINUX_NETFILTER
95 /* <climits> must be before including netfilter_ipv4.h */
96 #include <climits>
97 #include <linux/if.h>
98 #include <linux/netfilter_ipv4.h>
99 #if HAVE_LINUX_NETFILTER_IPV6_IP6_TABLES_H
100 /* 2013-07-01: Pablo the Netfilter maintainer is rejecting patches
101 * which will enable C++ compilers to build the Netfilter public headers.
102 * We can auto-detect its presence and attempt to use in case he ever
103 * changes his mind or things get cleaned up some other way.
104 * But until then are usually forced to hard-code the getsockopt() code
105 * for IPv6 NAT lookups.
106 */
107 #include <linux/netfilter_ipv6/ip6_tables.h>
108 #endif
109 #if !defined(IP6T_SO_ORIGINAL_DST)
110 #define IP6T_SO_ORIGINAL_DST 80 // stolen with prejudice from the above file.
111 #endif
112 #endif /* LINUX_NETFILTER required headers */
113
114 // single global instance for access by other components.
115 Ip::Intercept Ip::Interceptor;
116
117 void
118 Ip::Intercept::StopTransparency(const char *str)
119 {
120 if (transparentActive_) {
121 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
122 transparentActive_ = 0;
123 }
124 }
125
126 void
127 Ip::Intercept::StopInterception(const char *str)
128 {
129 if (interceptActive_) {
130 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
131 interceptActive_ = 0;
132 }
133 }
134
135 bool
136 Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
137 {
138 #if LINUX_NETFILTER
139 struct sockaddr_storage lookup;
140 socklen_t len = newConn->local.isIPv6() ? sizeof(sockaddr_in6) : sizeof(sockaddr_in);
141 newConn->local.getSockAddr(lookup, AF_UNSPEC);
142
143 /** \par
144 * Try NAT lookup for REDIRECT or DNAT targets. */
145 if ( getsockopt(newConn->fd,
146 newConn->local.isIPv6() ? IPPROTO_IPV6 : IPPROTO_IP,
147 newConn->local.isIPv6() ? IP6T_SO_ORIGINAL_DST : SO_ORIGINAL_DST,
148 &lookup,
149 &len) != 0) {
150 if (!silent) {
151 debugs(89, DBG_IMPORTANT, "ERROR: NF getsockopt(ORIGINAL_DST) failed on " << newConn << ": " << xstrerror());
152 lastReported_ = squid_curtime;
153 }
154 debugs(89, 9, "address: " << newConn);
155 return false;
156 } else {
157 newConn->local = lookup;
158 debugs(89, 5, "address NAT: " << newConn);
159 return true;
160 }
161 #endif
162 return false;
163 }
164
165 bool
166 Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer &newConn, int silent)
167 {
168 #if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \
169 (PF_TRANSPARENT && defined(SO_BINDANY)) || \
170 (IPFW_TRANSPARENT && defined(IP_BINDANY))
171
172 /* Trust the user configured properly. If not no harm done.
173 * We will simply attempt a bind outgoing on our own IP.
174 */
175 newConn->remote.port(0); // allow random outgoing port to prevent address clashes
176 debugs(89, 5, HERE << "address TPROXY: " << newConn);
177 return true;
178 #else
179 return false;
180 #endif
181 }
182
183 bool
184 Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int silent)
185 {
186 #if IPFW_TRANSPARENT
187 /* The getsockname() call performed already provided the TCP packet details.
188 * There is no way to identify whether they came from NAT or not.
189 * Trust the user configured properly.
190 */
191 debugs(89, 5, HERE << "address NAT: " << newConn);
192 return true;
193 #else
194 return false;
195 #endif
196 }
197
198 bool
199 Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
200 {
201 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
202
203 struct natlookup natLookup;
204 static int natfd = -1;
205 int x;
206
207 // all fields must be set to 0
208 memset(&natLookup, 0, sizeof(natLookup));
209 // for NAT lookup set local and remote IP:port's
210 natLookup.nl_inport = htons(newConn->local.port());
211 newConn->local.getInAddr(natLookup.nl_inip);
212 natLookup.nl_outport = htons(newConn->remote.port());
213 newConn->remote.getInAddr(natLookup.nl_outip);
214 // ... and the TCP flag
215 natLookup.nl_flags = IPN_TCP;
216
217 if (natfd < 0) {
218 int save_errno;
219 enter_suid();
220 #ifdef IPNAT_NAME
221 natfd = open(IPNAT_NAME, O_RDONLY, 0);
222 #else
223 natfd = open(IPL_NAT, O_RDONLY, 0);
224 #endif
225 save_errno = errno;
226 leave_suid();
227 errno = save_errno;
228 }
229
230 if (natfd < 0) {
231 if (!silent) {
232 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerror());
233 lastReported_ = squid_curtime;
234 return false;
235 }
236 }
237
238 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
239 struct ipfobj obj;
240 memset(&obj, 0, sizeof(obj));
241 obj.ipfo_rev = IPFILTER_VERSION;
242 obj.ipfo_size = sizeof(natLookup);
243 obj.ipfo_ptr = &natLookup;
244 obj.ipfo_type = IPFOBJ_NATLOOKUP;
245
246 x = ioctl(natfd, SIOCGNATL, &obj);
247 #else
248 /*
249 * IP-Filter changed the type for SIOCGNATL between
250 * 3.3 and 3.4. It also changed the cmd value for
251 * SIOCGNATL, so at least we can detect it. We could
252 * put something in configure and use ifdefs here, but
253 * this seems simpler.
254 */
255 static int siocgnatl_cmd = SIOCGNATL & 0xff;
256 if (63 == siocgnatl_cmd) {
257 struct natlookup *nlp = &natLookup;
258 x = ioctl(natfd, SIOCGNATL, &nlp);
259 } else {
260 x = ioctl(natfd, SIOCGNATL, &natLookup);
261 }
262
263 #endif
264 if (x < 0) {
265 if (errno != ESRCH) {
266 if (!silent) {
267 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerror());
268 lastReported_ = squid_curtime;
269 }
270
271 close(natfd);
272 natfd = -1;
273 }
274
275 debugs(89, 9, HERE << "address: " << newConn);
276 return false;
277 } else {
278 newConn->local = natLookup.nl_realip;
279 newConn->local.port(ntohs(natLookup.nl_realport));
280 debugs(89, 5, HERE << "address NAT: " << newConn);
281 return true;
282 }
283
284 #endif /* --enable-ipf-transparent */
285 return false;
286 }
287
288 bool
289 Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
290 {
291 #if PF_TRANSPARENT /* --enable-pf-transparent */
292
293 #if !USE_NAT_DEVPF
294 /* On recent PF versions the getsockname() call performed already provided
295 * the required TCP packet details.
296 * There is no way to identify whether they came from NAT or not.
297 *
298 * Trust the user configured properly.
299 */
300 debugs(89, 5, HERE << "address NAT divert-to: " << newConn);
301 return true;
302
303 #else /* USE_NAT_DEVPF / --with-nat-devpf */
304
305 struct pfioc_natlook nl;
306 static int pffd = -1;
307
308 if (pffd < 0)
309 pffd = open("/dev/pf", O_RDONLY);
310
311 if (pffd < 0) {
312 if (!silent) {
313 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
314 lastReported_ = squid_curtime;
315 }
316 return false;
317 }
318
319 memset(&nl, 0, sizeof(struct pfioc_natlook));
320 newConn->remote.getInAddr(nl.saddr.v4);
321 nl.sport = htons(newConn->remote.port());
322
323 newConn->local.getInAddr(nl.daddr.v4);
324 nl.dport = htons(newConn->local.port());
325
326 nl.af = AF_INET;
327 nl.proto = IPPROTO_TCP;
328 nl.direction = PF_OUT;
329
330 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
331 if (errno != ENOENT) {
332 if (!silent) {
333 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
334 lastReported_ = squid_curtime;
335 }
336 close(pffd);
337 pffd = -1;
338 }
339 debugs(89, 9, HERE << "address: " << newConn);
340 return false;
341 } else {
342 newConn->local = nl.rdaddr.v4;
343 newConn->local.port(ntohs(nl.rdport));
344 debugs(89, 5, HERE << "address NAT: " << newConn);
345 return true;
346 }
347 #endif /* --with-nat-devpf */
348 #endif /* --enable-pf-transparent */
349 return false;
350 }
351
352 bool
353 Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
354 {
355 /* --enable-linux-netfilter */
356 /* --enable-ipfw-transparent */
357 /* --enable-ipf-transparent */
358 /* --enable-pf-transparent */
359 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
360
361 #if 0
362 // Crop interception errors down to one per minute.
363 int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
364 #else
365 // Show all interception errors.
366 int silent = 0;
367 #endif
368
369 debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
370
371 newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
372
373 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
374 if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
375 if (TproxyTransparent(newConn, silent)) return true;
376 }
377
378 if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
379 /* NAT methods that use sock-opts to return client address */
380 if (NetfilterInterception(newConn, silent)) return true;
381 if (IpfwInterception(newConn, silent)) return true;
382
383 /* NAT methods that use ioctl to return client address AND destination address */
384 if (PfInterception(newConn, silent)) return true;
385 if (IpfInterception(newConn, silent)) return true;
386 }
387
388 #else /* none of the transparent options configured */
389 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
390 #endif
391
392 return false;
393 }
394
395 bool
396 Ip::Intercept::ProbeForTproxy(Ip::Address &test)
397 {
398 bool doneSuid = false;
399
400 #if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
401 # define soLevel SOL_IP
402 # define soFlag IP_TRANSPARENT
403
404 #elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF
405 # define soLevel SOL_SOCKET
406 # define soFlag SO_BINDANY
407 enter_suid();
408 doneSuid = true;
409
410 #elif defined(IP_BINDANY) // FreeBSD with IPFW
411 # define soLevel IPPROTO_IP
412 # define soFlag IP_BINDANY
413 enter_suid();
414 doneSuid = true;
415
416 #endif
417
418 #if defined(soLevel) && defined(soFlag)
419
420 debugs(3, 3, "Detect TPROXY support on port " << test);
421
422 int tos = 1;
423 int tmp_sock = -1;
424
425 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
426 if (test.isIPv6()) {
427 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
428
429 struct sockaddr_in6 tmp_ip6;
430 Ip::Address tmp = "::2";
431 tmp.port(0);
432 tmp.getSockAddr(tmp_ip6);
433
434 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
435 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
436 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
437
438 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
439 close(tmp_sock);
440 if (doneSuid)
441 leave_suid();
442 return true;
443 }
444 if (tmp_sock >= 0) {
445 close(tmp_sock);
446 tmp_sock = -1;
447 }
448 }
449
450 if ( test.isIPv6() && !test.setIPv4() ) {
451 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
452 if (doneSuid)
453 leave_suid();
454 return false;
455 }
456
457 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
458 if (test.isIPv4()) {
459 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
460
461 struct sockaddr_in tmp_ip4;
462 Ip::Address tmp = "127.0.0.2";
463 tmp.port(0);
464 tmp.getSockAddr(tmp_ip4);
465
466 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
467 setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 &&
468 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
469
470 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
471 close(tmp_sock);
472 if (doneSuid)
473 leave_suid();
474 return true;
475 }
476 if (tmp_sock >= 0) {
477 close(tmp_sock);
478 }
479 }
480
481 #else
482 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
483
484 #endif
485 if (doneSuid)
486 leave_suid();
487 return false;
488 }