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