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