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