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