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