]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ip/Intercept.cc
Fix enter_suid/leave_suid build errors in ip/Intercept.cc
[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 "ip/Intercept.h"
36 #include "fde.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 #if HAVE_LIMITS_H
96 /* must be before including netfilter_ipv4.h */
97 #include <limits.h>
98 #endif
99 #include <linux/netfilter_ipv4.h>
100 #endif /* LINUX_NETFILTER required headers */
101
102 // single global instance for access by other components.
103 Ip::Intercept Ip::Interceptor;
104
105 void
106 Ip::Intercept::StopTransparency(const char *str)
107 {
108 if (transparentActive_) {
109 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
110 transparentActive_ = 0;
111 }
112 }
113
114 void
115 Ip::Intercept::StopInterception(const char *str)
116 {
117 if (interceptActive_) {
118 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
119 interceptActive_ = 0;
120 }
121 }
122
123 bool
124 Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
125 {
126 #if LINUX_NETFILTER
127 struct sockaddr_in lookup;
128 socklen_t len = sizeof(struct sockaddr_in);
129 newConn->local.GetSockAddr(lookup);
130
131 /** \par
132 * Try NAT lookup for REDIRECT or DNAT targets. */
133 if ( getsockopt(newConn->fd, IPPROTO_IP, SO_ORIGINAL_DST, &lookup, &len) != 0) {
134 if (!silent) {
135 debugs(89, DBG_IMPORTANT, HERE << " NF getsockopt(SO_ORIGINAL_DST) failed on " << newConn << ": " << xstrerror());
136 lastReported_ = squid_curtime;
137 }
138 debugs(89, 9, HERE << "address: " << newConn);
139 return false;
140 } else {
141 newConn->local = lookup;
142 debugs(89, 5, HERE << "address NAT: " << newConn);
143 return true;
144 }
145 #endif
146 return false;
147 }
148
149 bool
150 Ip::Intercept::NetfilterTransparent(const Comm::ConnectionPointer &newConn, int silent)
151 {
152 #if LINUX_NETFILTER
153 /* Trust the user configured properly. If not no harm done.
154 * We will simply attempt a bind outgoing on our own IP.
155 */
156 newConn->remote.SetPort(0); // allow random outgoing port to prevent address clashes
157 debugs(89, 5, HERE << "address TPROXY: " << newConn);
158 return true;
159 #else
160 return false;
161 #endif
162 }
163
164 bool
165 Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int silent)
166 {
167 #if IPFW_TRANSPARENT
168 struct sockaddr_storage lookup;
169 socklen_t len = sizeof(struct sockaddr_storage);
170 newConn->local.GetSockAddr(lookup, AF_INET);
171
172 /** \par
173 * Try lookup for IPFW interception. */
174 if ( getsockname(newConn->fd, (struct sockaddr*)&lookup, &len) != 0 ) {
175 if ( !silent ) {
176 debugs(89, DBG_IMPORTANT, HERE << " IPFW getsockname(...) failed: " << xstrerror());
177 lastReported_ = squid_curtime;
178 }
179 debugs(89, 9, HERE << "address: " << newConn);
180 return false;
181 } else {
182 newConn->local = lookup;
183 debugs(89, 5, HERE << "address NAT: " << newConn);
184 return true;
185 }
186 #endif
187 return false;
188 }
189
190 bool
191 Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
192 {
193 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
194
195 struct natlookup natLookup;
196 static int natfd = -1;
197 int x;
198
199 // all fields must be set to 0
200 memset(&natLookup, 0, sizeof(natLookup));
201 // for NAT lookup set local and remote IP:port's
202 natLookup.nl_inport = htons(newConn->local.GetPort());
203 newConn->local.GetInAddr(natLookup.nl_inip);
204 natLookup.nl_outport = htons(newConn->remote.GetPort());
205 newConn->remote.GetInAddr(natLookup.nl_outip);
206 // ... and the TCP flag
207 natLookup.nl_flags = IPN_TCP;
208
209 if (natfd < 0) {
210 int save_errno;
211 enter_suid();
212 #ifdef IPNAT_NAME
213 natfd = open(IPNAT_NAME, O_RDONLY, 0);
214 #else
215 natfd = open(IPL_NAT, O_RDONLY, 0);
216 #endif
217 save_errno = errno;
218 leave_suid();
219 errno = save_errno;
220 }
221
222 if (natfd < 0) {
223 if (!silent) {
224 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerror());
225 lastReported_ = squid_curtime;
226 return false;
227 }
228 }
229
230 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
231 struct ipfobj obj;
232 memset(&obj, 0, sizeof(obj));
233 obj.ipfo_rev = IPFILTER_VERSION;
234 obj.ipfo_size = sizeof(natLookup);
235 obj.ipfo_ptr = &natLookup;
236 obj.ipfo_type = IPFOBJ_NATLOOKUP;
237
238 x = ioctl(natfd, SIOCGNATL, &obj);
239 #else
240 /*
241 * IP-Filter changed the type for SIOCGNATL between
242 * 3.3 and 3.4. It also changed the cmd value for
243 * SIOCGNATL, so at least we can detect it. We could
244 * put something in configure and use ifdefs here, but
245 * this seems simpler.
246 */
247 static int siocgnatl_cmd = SIOCGNATL & 0xff;
248 if (63 == siocgnatl_cmd) {
249 struct natlookup *nlp = &natLookup;
250 x = ioctl(natfd, SIOCGNATL, &nlp);
251 } else {
252 x = ioctl(natfd, SIOCGNATL, &natLookup);
253 }
254
255 #endif
256 if (x < 0) {
257 if (errno != ESRCH) {
258 if (!silent) {
259 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerror());
260 lastReported_ = squid_curtime;
261 }
262
263 close(natfd);
264 natfd = -1;
265 }
266
267 debugs(89, 9, HERE << "address: " << newConn);
268 return false;
269 } else {
270 newConn->local = natLookup.nl_realip;
271 newConn->local.SetPort(ntohs(natLookup.nl_realport));
272 debugs(89, 5, HERE << "address NAT: " << newConn);
273 return true;
274 }
275
276 #endif /* --enable-ipf-transparent */
277 return false;
278 }
279
280 bool
281 Ip::Intercept::PfTransparent(const Comm::ConnectionPointer &newConn, int silent)
282 {
283 #if PF_TRANSPARENT && defined(SO_BINDANY)
284 /* Trust the user configured properly. If not no harm done.
285 * We will simply attempt a bind outgoing on our own IP.
286 */
287 newConn->remote.SetPort(0); // allow random outgoing port to prevent address clashes
288 debugs(89, 5, HERE << "address DIVERT: " << newConn);
289 return true;
290 #else
291 return false;
292 #endif
293 }
294
295 bool
296 Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
297 {
298 #if PF_TRANSPARENT /* --enable-pf-transparent */
299
300 struct pfioc_natlook nl;
301 static int pffd = -1;
302
303 if (pffd < 0)
304 pffd = open("/dev/pf", O_RDONLY);
305
306 if (pffd < 0) {
307 if (!silent) {
308 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
309 lastReported_ = squid_curtime;
310 }
311 return false;
312 }
313
314 memset(&nl, 0, sizeof(struct pfioc_natlook));
315 newConn->remote.GetInAddr(nl.saddr.v4);
316 nl.sport = htons(newConn->remote.GetPort());
317
318 newConn->local.GetInAddr(nl.daddr.v4);
319 nl.dport = htons(newConn->local.GetPort());
320
321 nl.af = AF_INET;
322 nl.proto = IPPROTO_TCP;
323 nl.direction = PF_OUT;
324
325 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
326 if (errno != ENOENT) {
327 if (!silent) {
328 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
329 lastReported_ = squid_curtime;
330 }
331 close(pffd);
332 pffd = -1;
333 }
334 debugs(89, 9, HERE << "address: " << newConn);
335 return false;
336 } else {
337 newConn->local = nl.rdaddr.v4;
338 newConn->local.SetPort(ntohs(nl.rdport));
339 debugs(89, 5, HERE << "address NAT: " << newConn);
340 return true;
341 }
342
343 #endif /* --enable-pf-transparent */
344 return false;
345 }
346
347 bool
348 Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
349 {
350 /* --enable-linux-netfilter */
351 /* --enable-ipfw-transparent */
352 /* --enable-ipf-transparent */
353 /* --enable-pf-transparent */
354 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
355
356 #if 0
357 // Crop interception errors down to one per minute.
358 int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
359 #else
360 // Show all interception errors.
361 int silent = 0;
362 #endif
363
364 debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
365
366 newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
367
368 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
369 if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
370 if (NetfilterTransparent(newConn, silent)) return true;
371 if (PfTransparent(newConn, silent)) return true;
372 }
373
374 /* NAT is only available in IPv4 */
375 if ( !newConn->local.IsIPv4() ) return false;
376 if ( !newConn->remote.IsIPv4() ) return false;
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 #if defined(IP_TRANSPARENT)
399 debugs(3, 3, "Detect TPROXY support on port " << test);
400
401 int tos = 1;
402 int tmp_sock = -1;
403
404 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
405 if (test.IsIPv6()) {
406 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
407
408 struct sockaddr_in6 tmp_ip6;
409 Ip::Address tmp = "::2";
410 tmp.SetPort(0);
411 tmp.GetSockAddr(tmp_ip6);
412
413 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
414 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
415 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
416
417 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
418 close(tmp_sock);
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 return false;
430 }
431
432 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
433 if (test.IsIPv4()) {
434 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
435
436 struct sockaddr_in tmp_ip4;
437 Ip::Address tmp = "127.0.0.2";
438 tmp.SetPort(0);
439 tmp.GetSockAddr(tmp_ip4);
440
441 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
442 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
443 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
444
445 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
446 close(tmp_sock);
447 return true;
448 }
449 if (tmp_sock >= 0) {
450 close(tmp_sock);
451 }
452 }
453
454 #elif defined(SO_BINDANY)
455 debugs(3, 3, "Detect BINDANY support on port " << test);
456
457 int tos = 1;
458 int tmp_sock = -1;
459
460 if (test.IsIPv6()) {
461 debugs(3, 3, "...Probing for IPv6 SO_BINDANY support.");
462
463 struct sockaddr_in6 tmp_ip6;
464 Ip::Address tmp = "::2";
465 tmp.SetPort(0);
466 tmp.GetSockAddr(tmp_ip6);
467
468 if ((tmp_sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) >=0 &&
469 (setsockopt(tmp_sock, SOL_SOCKET, SO_BINDANY, (char *)&tos,
470 sizeof(tos)) == 0) &&
471 (bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0)) {
472 debugs(3, 3, "IPv6 BINDANY support detected. Using.");
473 close(tmp_sock);
474 return true;
475 }
476 }
477
478 if (test.IsIPv4()) {
479 debugs(3, 3, "...Probing for IPv4 SO_BINDANY support.");
480
481 struct sockaddr_in tmp_ip4;
482 Ip::Address tmp = "127.0.0.2";
483 tmp.SetPort(0);
484 tmp.GetSockAddr(tmp_ip4);
485
486 if ((tmp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) >=0 &&
487 (setsockopt(tmp_sock, SOL_SOCKET, SO_BINDANY, (char *)&tos,
488 sizeof(tos)) == 0) &&
489 (bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0)) {
490 debugs(3, 3, "IPv4 BINDANY support detected. Using.");
491 close(tmp_sock);
492 return true;
493 }
494 }
495
496 #else
497 debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
498
499 #endif
500 return false;
501 }